cee4cf6452
## Summary - Migrates 4 entities (`connectedAccount`, `messageChannel`, `calendarChannel`, `messageFolder`) from per-workspace schemas to the shared `core` metadata schema - Introduces a `IS_CONNECTED_ACCOUNT_MIGRATED` feature flag to control the migration: when enabled, reads come from core metadata and all writes are dual-written to both workspace and core - Extracts 12 enums from workspace entity files to `twenty-shared` for reuse across frontend and backend - Creates new TypeORM entities, metadata services, GraphQL resolvers/DTOs, and exception interceptors per entity - Each entity owns its own data access module (`ConnectedAccountDataAccessModule`, `MessageChannelDataAccessModule`, `CalendarChannelDataAccessModule`, `MessageFolderDataAccessModule`) — no umbrella infrastructure module - Adds a 1.20 upgrade command that backfills data from workspace schemas to core (preserving UUIDs) and enables the feature flag - Replaces direct repository access with data access service calls across ~50 files in messaging, calendar, and connected-account modules - Adds `lastSignedInAt` and `oidcTokenClaims` fields to the new `ConnectedAccountEntity` - Drops unused `lastSyncHistoryId` field from the migrated connected account entity ## Test plan - [x] Lint passes (`npx nx lint:diff-with-main twenty-server`) - [x] Typecheck passes (`npx nx typecheck twenty-server`) - [x] All unit tests pass (477 suites, 4267 tests, 0 failures) - [ ] Manual test: verify messaging sync works with feature flag disabled (existing behavior) - [ ] Manual test: run upgrade command on a workspace, verify data backfilled to core tables - [ ] Manual test: verify messaging/calendar sync works with feature flag enabled (dual-write path) - [ ] Manual test: verify GraphQL metadata resolvers return correct data when flag enabled
135 lines
3.4 KiB
TypeScript
135 lines
3.4 KiB
TypeScript
import {
|
|
Column,
|
|
CreateDateColumn,
|
|
Entity,
|
|
JoinColumn,
|
|
ManyToOne,
|
|
OneToMany,
|
|
PrimaryGeneratedColumn,
|
|
type Relation,
|
|
UpdateDateColumn,
|
|
} from 'typeorm';
|
|
|
|
import {
|
|
MessageChannelContactAutoCreationPolicy,
|
|
MessageChannelPendingGroupEmailsAction,
|
|
MessageChannelSyncStage,
|
|
MessageChannelSyncStatus,
|
|
MessageChannelType,
|
|
MessageChannelVisibility,
|
|
MessageFolderImportPolicy,
|
|
} from 'twenty-shared/types';
|
|
|
|
import { ConnectedAccountEntity } from 'src/engine/metadata-modules/connected-account/entities/connected-account.entity';
|
|
import { type MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity';
|
|
import { WorkspaceRelatedEntity } from 'src/engine/workspace-manager/types/workspace-related-entity';
|
|
|
|
@Entity({ name: 'messageChannel', schema: 'core' })
|
|
export class MessageChannelEntity extends WorkspaceRelatedEntity {
|
|
@PrimaryGeneratedColumn('uuid')
|
|
id: string;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelVisibility,
|
|
nullable: false,
|
|
})
|
|
visibility: MessageChannelVisibility;
|
|
|
|
@Column({ type: 'varchar', nullable: false })
|
|
handle: string;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelType,
|
|
nullable: false,
|
|
})
|
|
type: MessageChannelType;
|
|
|
|
@Column({ type: 'boolean', nullable: false })
|
|
isContactAutoCreationEnabled: boolean;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelContactAutoCreationPolicy,
|
|
nullable: false,
|
|
})
|
|
contactAutoCreationPolicy: MessageChannelContactAutoCreationPolicy;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageFolderImportPolicy,
|
|
nullable: false,
|
|
})
|
|
messageFolderImportPolicy: MessageFolderImportPolicy;
|
|
|
|
@Column({ type: 'boolean', nullable: false })
|
|
excludeNonProfessionalEmails: boolean;
|
|
|
|
@Column({ type: 'boolean', nullable: false })
|
|
excludeGroupEmails: boolean;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelPendingGroupEmailsAction,
|
|
nullable: false,
|
|
})
|
|
pendingGroupEmailsAction: MessageChannelPendingGroupEmailsAction;
|
|
|
|
@Column({ type: 'boolean', nullable: false })
|
|
isSyncEnabled: boolean;
|
|
|
|
@Column({ type: 'varchar', nullable: true })
|
|
syncCursor: string | null;
|
|
|
|
@Column({ type: 'timestamptz', nullable: true })
|
|
syncedAt: Date | null;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelSyncStatus,
|
|
nullable: false,
|
|
default: MessageChannelSyncStatus.NOT_SYNCED,
|
|
})
|
|
syncStatus: MessageChannelSyncStatus;
|
|
|
|
@Column({
|
|
type: 'enum',
|
|
enum: MessageChannelSyncStage,
|
|
nullable: false,
|
|
})
|
|
syncStage: MessageChannelSyncStage;
|
|
|
|
@Column({ type: 'timestamptz', nullable: true })
|
|
syncStageStartedAt: Date | null;
|
|
|
|
@Column({ type: 'integer', nullable: false, default: 0 })
|
|
throttleFailureCount: number;
|
|
|
|
@Column({ type: 'timestamptz', nullable: true })
|
|
throttleRetryAfter: Date | null;
|
|
|
|
@Column({ type: 'uuid', nullable: false })
|
|
connectedAccountId: string;
|
|
|
|
@ManyToOne(
|
|
() => ConnectedAccountEntity,
|
|
(connectedAccount) => connectedAccount.messageChannels,
|
|
{ onDelete: 'CASCADE' },
|
|
)
|
|
@JoinColumn({ name: 'connectedAccountId' })
|
|
connectedAccount: Relation<ConnectedAccountEntity>;
|
|
|
|
@OneToMany(
|
|
'MessageFolderEntity',
|
|
(messageFolder: MessageFolderEntity) => messageFolder.messageChannel,
|
|
)
|
|
messageFolders: Relation<MessageFolderEntity[]>;
|
|
|
|
@CreateDateColumn({ type: 'timestamptz' })
|
|
createdAt: Date;
|
|
|
|
@UpdateDateColumn({ type: 'timestamptz' })
|
|
updatedAt: Date;
|
|
}
|