messaging: gmail folder backfill (#21753)

demo


https://github.com/user-attachments/assets/a157cee1-a8fa-4050-af1b-c31a83fb75da

/closes #17095


<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/21753?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
neo773
2026-06-19 05:29:35 +05:30
committed by GitHub
parent d88eb6c16b
commit 616d58bc7e
21 changed files with 223 additions and 56 deletions
@@ -1,22 +1,15 @@
import { Field, InputType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsUUID,
ValidateNested,
} from 'class-validator';
import { IsBoolean, IsNotEmpty, IsUUID, ValidateNested } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class UpdateMessageFolderInputUpdates {
@IsOptional()
@IsBoolean()
@Field({ nullable: true })
isSynced?: boolean;
@Field()
isSynced: boolean;
}
@InputType()
@@ -3,7 +3,10 @@ import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import { MessageFolderPendingSyncAction } from 'twenty-shared/types';
import {
ConnectedAccountProvider,
MessageFolderPendingSyncAction,
} from 'twenty-shared/types';
import { ConnectedAccountMetadataService } from 'src/engine/metadata-modules/connected-account/connected-account-metadata.service';
import { MessageFolderDTO } from 'src/engine/metadata-modules/message-folder/dtos/message-folder.dto';
@@ -183,7 +186,7 @@ export class MessageFolderMetadataService {
return this.repository.findOneOrFail({ where: { id, workspaceId } });
}
async updateMany({
async setSyncStatus({
ids,
workspaceId,
data,
@@ -192,10 +195,53 @@ export class MessageFolderMetadataService {
workspaceId: string;
data: Partial<MessageFolderEntity>;
}): Promise<MessageFolderDTO[]> {
await this.repository.update(
{ id: In(ids), workspaceId },
data as Record<string, unknown>,
);
await this.repository.manager.transaction(async (manager) => {
if (!data.isSynced) {
await manager.update(
MessageFolderEntity,
{ id: In(ids), workspaceId },
{ isSynced: false },
);
await manager.update(
MessageFolderEntity,
{
id: In(ids),
workspaceId,
pendingSyncAction: MessageFolderPendingSyncAction.FOLDER_IMPORT,
},
{ pendingSyncAction: MessageFolderPendingSyncAction.NONE },
);
return;
}
const folderIdsToBackfill = (
await manager.find(MessageFolderEntity, {
where: {
id: In(ids),
workspaceId,
isSynced: false,
messageChannel: {
connectedAccount: { provider: ConnectedAccountProvider.GOOGLE },
},
},
})
).map((folder) => folder.id);
await manager.update(
MessageFolderEntity,
{ id: In(ids), workspaceId },
{ isSynced: true },
);
if (folderIdsToBackfill.length > 0) {
await manager.update(
MessageFolderEntity,
{ id: In(folderIdsToBackfill), workspaceId },
{ pendingSyncAction: MessageFolderPendingSyncAction.FOLDER_IMPORT },
);
}
});
return this.repository.find({ where: { id: In(ids), workspaceId } });
}
@@ -86,7 +86,7 @@ export class MessageFolderResolver {
),
);
return this.messageFolderMetadataService.updateMany({
return this.messageFolderMetadataService.setSyncStatus({
ids: input.ids,
workspaceId: workspace.id,
data: input.update,