import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { In, Repository } from 'typeorm'; 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'; import { MessageFolderEntity } from 'src/engine/metadata-modules/message-folder/entities/message-folder.entity'; import { MessageFolderException, MessageFolderExceptionCode, } from 'src/engine/metadata-modules/message-folder/message-folder.exception'; import { MessageChannelMetadataService } from 'src/engine/metadata-modules/message-channel/message-channel-metadata.service'; @Injectable() export class MessageFolderMetadataService { constructor( @InjectRepository(MessageFolderEntity) private readonly repository: Repository, private readonly messageChannelMetadataService: MessageChannelMetadataService, private readonly connectedAccountMetadataService: ConnectedAccountMetadataService, ) {} async findAll(workspaceId: string): Promise { return this.repository.find({ where: { workspaceId } }); } async findByUserWorkspaceId({ userWorkspaceId, workspaceId, }: { userWorkspaceId: string; workspaceId: string; }): Promise { const userAccountIds = await this.connectedAccountMetadataService.getUserConnectedAccountIds({ userWorkspaceId, workspaceId, }); const userChannels = await this.messageChannelMetadataService.findByConnectedAccountIds({ connectedAccountIds: userAccountIds, workspaceId, }); const userChannelIds = userChannels.map((channel) => channel.id); return this.findByMessageChannelIds({ messageChannelIds: userChannelIds, workspaceId, }); } async findByMessageChannelIdForUser({ messageChannelId, userWorkspaceId, workspaceId, }: { messageChannelId: string; userWorkspaceId: string; workspaceId: string; }): Promise { await this.messageChannelMetadataService.verifyOwnership({ id: messageChannelId, userWorkspaceId, workspaceId, }); return this.findByMessageChannelId({ messageChannelId, workspaceId }); } async findByMessageChannelId({ messageChannelId, workspaceId, }: { messageChannelId: string; workspaceId: string; }): Promise { return this.repository.find({ where: { messageChannelId, workspaceId }, }); } async findByMessageChannelIds({ messageChannelIds, workspaceId, }: { messageChannelIds: string[]; workspaceId: string; }): Promise { if (messageChannelIds.length === 0) { return []; } return this.repository.find({ where: { messageChannelId: In(messageChannelIds), workspaceId }, }); } async findById({ id, workspaceId, }: { id: string; workspaceId: string; }): Promise { return this.repository.findOne({ where: { id, workspaceId } }); } async verifyOwnership({ id, userWorkspaceId, workspaceId, }: { id: string; userWorkspaceId: string; workspaceId: string; }): Promise { const messageFolder = await this.repository.findOne({ where: { id, workspaceId }, }); if (!messageFolder) { throw new MessageFolderException( `Message folder ${id} not found`, MessageFolderExceptionCode.MESSAGE_FOLDER_NOT_FOUND, ); } const userAccountIds = await this.connectedAccountMetadataService.getUserConnectedAccountIds({ userWorkspaceId, workspaceId, }); const messageChannel = await this.messageChannelMetadataService.findById({ id: messageFolder.messageChannelId, workspaceId, }); if ( !messageChannel || !userAccountIds.includes(messageChannel.connectedAccountId) ) { throw new MessageFolderException( `Message folder ${id} does not belong to user workspace ${userWorkspaceId}`, MessageFolderExceptionCode.MESSAGE_FOLDER_OWNERSHIP_VIOLATION, ); } return messageFolder; } async create( data: Partial & { workspaceId: string; messageChannelId: string; pendingSyncAction: MessageFolderPendingSyncAction; }, ): Promise { const entity = this.repository.create(data); return this.repository.save(entity); } async update({ id, workspaceId, data, }: { id: string; workspaceId: string; data: Partial; }): Promise { await this.repository.update( { id, workspaceId }, data as Record, ); return this.repository.findOneOrFail({ where: { id, workspaceId } }); } async setSyncStatus({ ids, workspaceId, data, }: { ids: string[]; workspaceId: string; data: Partial; }): Promise { 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 } }); } async delete({ id, workspaceId, }: { id: string; workspaceId: string; }): Promise { const messageFolder = await this.repository.findOneOrFail({ where: { id, workspaceId }, }); await this.repository.delete({ id, workspaceId }); return messageFolder; } }