Update workspace entities to make all TEXT nullable (#16144)

Follow up on #15926

---------

Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
Charles Bochet
2025-11-28 10:50:38 +01:00
committed by GitHub
parent 9620a4b0ba
commit eb362c6d5f
63 changed files with 396 additions and 136 deletions
@@ -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: {
@@ -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 ?? '',
};
});
@@ -31,7 +31,7 @@ export class FileAttachmentListener {
FileDeletionJob.name,
{
workspaceId: payload.workspaceId,
fullPath: event.properties.before.fullPath,
fullPath: event.properties.before.fullPath ?? '',
},
);
}
@@ -35,7 +35,7 @@ export class FileWorkspaceMemberListener {
this.messageQueueService.add<FileDeletionJobData>(FileDeletionJob.name, {
workspaceId: payload.workspaceId,
fullPath: event.properties.before.avatarUrl,
fullPath: event.properties.before.avatarUrl ?? '',
});
}
}
@@ -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`,
);
@@ -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,
};
@@ -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,
};
};
@@ -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,
@@ -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)
@@ -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: {
@@ -7,8 +7,11 @@ export const transformStandardAgentDefinitionToFlatAgent = (
standardAgentDefinition: StandardAgentDefinition,
workspaceId: string,
): FlatAgent => {
const { standardRoleId: _standardRoleId, ...agentData } =
standardAgentDefinition;
const {
standardRoleId: _standardRoleId,
outputStrategy: _outputStrategy,
...agentData
} = standardAgentDefinition;
return {
...agentData,
@@ -7,9 +7,9 @@ import {
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata.constants';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
@@ -44,7 +44,8 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
type: FieldMetadataType.TEXT,
icon: 'IconAbc',
})
name: string;
@WorkspaceIsNullable()
name: string | null;
@WorkspaceField({
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
@@ -133,6 +133,10 @@ export class CleanerWorkspaceService {
const i18n = this.i18nService.getI18nInstance(workspaceMember.locale);
const subject = i18n._(workspaceDeletionMsg);
if (!isDefined(workspaceMember.userEmail)) {
throw new Error('Workspace member email is missing');
}
this.emailService.send({
to: workspaceMember.userEmail,
from: `${this.twentyConfigService.get(
@@ -207,6 +211,10 @@ export class CleanerWorkspaceService {
const html = await render(emailTemplate, { pretty: true });
const text = await render(emailTemplate, { plainText: true });
if (!isDefined(workspaceMember.userEmail)) {
throw new Error('Workspace member email is missing');
}
this.emailService.send({
to: workspaceMember.userEmail,
from: `${this.twentyConfigService.get(
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
import { fromStandardRoleDefinitionToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-standard-role-definition-to-flat-role.util';
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
@@ -13,8 +12,8 @@ export class StandardRoleFactory {
roleDefinitions: StandardRoleDefinition[],
context: WorkspaceSyncContext,
existingRoles: RoleEntity[],
): FlatRole[] {
const computedRoles: FlatRole[] = [];
): Partial<RoleEntity>[] {
const computedRoles: Partial<RoleEntity>[] = [];
for (const roleDefinition of roleDefinitions) {
const existingRole = existingRoles.find(
@@ -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) {