[permissions] Add permissions check layer in entityManager (#11818)

First and main step of
https://github.com/twentyhq/core-team-issues/issues/747

We are implementing a permission check layer in our custom
WorkspaceEntityManager by overriding all the db-executing methods (this
PR only overrides some as a POC, the rest will be done in the next PR).
Our custom repositories call entity managers under the hood to interact
with the db so this solves the repositories case too.
This is still behind the feature flag IsPermissionsV2Enabled.

In the next PR
- finish overriding all the methods required in WorkspaceEntityManager
- add tests
This commit is contained in:
Marie
2025-05-05 16:06:54 +02:00
committed by GitHub
parent 5f8040af5d
commit a9e73c6340
62 changed files with 1194 additions and 933 deletions

View File

@ -1,7 +1,7 @@
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { Command, CommandRunner } from 'nest-commander'; import { Command, CommandRunner } from 'nest-commander';
import { DataSource, EntityManager } from 'typeorm'; import { DataSource } from 'typeorm';
import { seedCoreSchema } from 'src/database/typeorm-seeds/core'; import { seedCoreSchema } from 'src/database/typeorm-seeds/core';
import { import {
@ -43,6 +43,7 @@ import { SURVEY_RESULTS_DATA_SEEDS } from 'src/engine/seeder/data-seeds/survey-r
import { PETS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/pets-metadata-seeds'; import { PETS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/pets-metadata-seeds';
import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds'; import { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds';
import { SeederService } from 'src/engine/seeder/seeder.service'; import { SeederService } from 'src/engine/seeder/seeder.service';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views'; import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views';
import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data'; import { seedViewWithDemoData } from 'src/engine/workspace-manager/standard-objects-prefill-data/seed-view-with-demo-data';
@ -169,7 +170,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
dataSourceMetadata: DataSourceEntity, dataSourceMetadata: DataSourceEntity,
) { ) {
await workspaceDataSource.transaction( await workspaceDataSource.transaction(
async (entityManager: EntityManager) => { async (entityManager: WorkspaceEntityManager) => {
const { objectMetadataStandardIdToIdMap } = const { objectMetadataStandardIdToIdMap } =
await this.objectMetadataService.getObjectMetadataStandardIdToIdMap( await this.objectMetadataService.getObjectMetadataStandardIdToIdMap(
dataSourceMetadata.workspaceId, dataSourceMetadata.workspaceId,

View File

@ -1,15 +1,17 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'apiKey'; const tableName = 'apiKey';
const API_KEY_ID = '20202020-f401-4d8a-a731-64d007c27bad'; const API_KEY_ID = '20202020-f401-4d8a-a731-64d007c27bad';
export const seedApiKey = async ( export const seedApiKey = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, ['id', 'name', 'expiresAt']) .into(`${schemaName}.${tableName}`, ['id', 'name', 'expiresAt'])
.orIgnore() .orIgnore()

View File

@ -1,13 +1,15 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'calendarChannelEventAssociation'; const tableName = 'calendarChannelEventAssociation';
export const seedCalendarChannelEventAssociations = async ( export const seedCalendarChannelEventAssociations = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,16 +1,17 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account'; import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity'; import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
const tableName = 'calendarChannel'; const tableName = 'calendarChannel';
export const seedCalendarChannels = async ( export const seedCalendarChannels = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,17 +1,18 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople'; import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { CalendarEventParticipantResponseStatus } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { CalendarEventParticipantResponseStatus } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
const tableName = 'calendarEventParticipant'; const tableName = 'calendarEventParticipant';
export const seedCalendarEventParticipants = async ( export const seedCalendarEventParticipants = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,13 +1,15 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'calendarEvent'; const tableName = 'calendarEvent';
export const seedCalendarEvents = async ( export const seedCalendarEvents = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'company'; const tableName = 'company';
@ -21,11 +20,13 @@ export const DEV_SEED_COMPANY_IDS = {
}; };
export const seedCompanies = async ( export const seedCompanies = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'connectedAccount'; const tableName = 'connectedAccount';
@ -11,11 +10,13 @@ export const DEV_SEED_CONNECTED_ACCOUNT_IDS = {
}; };
export const seedConnectedAccount = async ( export const seedConnectedAccount = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,15 +1,18 @@
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'favorite'; const tableName = 'favorite';
export const seedWorkspaceFavorites = async ( export const seedWorkspaceFavorites = async (
viewIds: string[], viewIds: string[],
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, ['id', 'viewId', 'position']) .into(`${schemaName}.${tableName}`, ['id', 'viewId', 'position'])
.values( .values(

View File

@ -1,7 +1,6 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_MESSAGE_CHANNEL_IDS } from 'src/database/typeorm-seeds/workspace/message-channels'; import { DEV_SEED_MESSAGE_CHANNEL_IDS } from 'src/database/typeorm-seeds/workspace/message-channels';
import { DEV_SEED_MESSAGE_IDS } from 'src/database/typeorm-seeds/workspace/messages'; import { DEV_SEED_MESSAGE_IDS } from 'src/database/typeorm-seeds/workspace/messages';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum'; import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
const tableName = 'messageChannelMessageAssociation'; const tableName = 'messageChannelMessageAssociation';
@ -13,11 +12,13 @@ export const DEV_SEED_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_IDS = {
}; };
export const seedMessageChannelMessageAssociation = async ( export const seedMessageChannelMessageAssociation = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account'; import { DEV_SEED_CONNECTED_ACCOUNT_IDS } from 'src/database/typeorm-seeds/workspace/connected-account';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { import {
MessageChannelSyncStage, MessageChannelSyncStage,
MessageChannelVisibility, MessageChannelVisibility,
@ -15,11 +14,13 @@ export const DEV_SEED_MESSAGE_CHANNEL_IDS = {
}; };
export const seedMessageChannel = async ( export const seedMessageChannel = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,8 +1,7 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_MESSAGE_IDS } from 'src/database/typeorm-seeds/workspace/messages'; import { DEV_SEED_MESSAGE_IDS } from 'src/database/typeorm-seeds/workspace/messages';
import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople'; import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'messageParticipant'; const tableName = 'messageParticipant';
@ -16,11 +15,13 @@ export const DEV_SEED_MESSAGE_PARTICIPANT_IDS = {
}; };
export const seedMessageParticipant = async ( export const seedMessageParticipant = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,4 +1,4 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'messageThreadSubscriber'; const tableName = 'messageThreadSubscriber';
@ -26,11 +26,13 @@ export const DEV_SEED_USER_IDS = {
}; };
export const seedMessageThreadSubscribers = async ( export const seedMessageThreadSubscribers = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,4 +1,4 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'messageThread'; const tableName = 'messageThread';
@ -11,11 +11,13 @@ export const DEV_SEED_MESSAGE_THREAD_IDS = {
}; };
export const seedMessageThread = async ( export const seedMessageThread = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_MESSAGE_THREAD_IDS } from 'src/database/typeorm-seeds/workspace/message-threads'; import { DEV_SEED_MESSAGE_THREAD_IDS } from 'src/database/typeorm-seeds/workspace/message-threads';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'message'; const tableName = 'message';
@ -11,11 +10,13 @@ export const DEV_SEED_MESSAGE_IDS = {
}; };
export const seedMessage = async ( export const seedMessage = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,8 +1,7 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_COMPANY_IDS } from 'src/database/typeorm-seeds/workspace/companies'; import { DEV_SEED_COMPANY_IDS } from 'src/database/typeorm-seeds/workspace/companies';
import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople'; import { DEV_SEED_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'opportunity'; const tableName = 'opportunity';
@ -14,11 +13,13 @@ export const DEV_SEED_OPPORTUNITY_IDS = {
}; };
export const seedOpportunity = async ( export const seedOpportunity = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,7 +1,6 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_COMPANY_IDS } from 'src/database/typeorm-seeds/workspace/companies'; import { DEV_SEED_COMPANY_IDS } from 'src/database/typeorm-seeds/workspace/companies';
import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members'; import { DEV_SEED_WORKSPACE_MEMBER_IDS } from 'src/database/typeorm-seeds/workspace/workspace-members';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'person'; const tableName = 'person';
@ -24,11 +23,13 @@ export const DEV_SEED_PERSON_IDS = {
}; };
export const seedPeople = async ( export const seedPeople = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,11 +1,10 @@
import { EntityManager } from 'typeorm'; import { DEV_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/users';
import { import {
SEED_APPLE_WORKSPACE_ID,
SEED_ACME_WORKSPACE_ID, SEED_ACME_WORKSPACE_ID,
SEED_APPLE_WORKSPACE_ID,
} from 'src/database/typeorm-seeds/core/workspaces'; } from 'src/database/typeorm-seeds/core/workspaces';
import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto'; import { WorkspaceMember } from 'src/engine/core-modules/user/dtos/workspace-member.dto';
import { DEV_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/users'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'workspaceMember'; const tableName = 'workspaceMember';
@ -26,7 +25,7 @@ type WorkspaceMembers = Pick<
}; };
export const seedWorkspaceMember = async ( export const seedWorkspaceMember = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
workspaceId: string, workspaceId: string,
) => { ) => {
@ -78,7 +77,9 @@ export const seedWorkspaceMember = async (
]; ];
} }
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { EntityManager, Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@ -12,6 +12,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { import {
@ -103,202 +104,208 @@ export class GoogleAPIsService {
const scopes = getGoogleApisOauthScopes(); const scopes = getGoogleApisOauthScopes();
await workspaceDataSource.transaction(async (manager: EntityManager) => { await workspaceDataSource.transaction(
if (!existingAccountId) { async (manager: WorkspaceEntityManager) => {
const newConnectedAccount = await connectedAccountRepository.save( if (!existingAccountId) {
{ const newConnectedAccount = await connectedAccountRepository.save(
id: newOrExistingConnectedAccountId,
handle,
provider: ConnectedAccountProvider.GOOGLE,
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
},
{},
manager,
);
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED,
events: [
{ {
recordId: newConnectedAccount.id, id: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
after: newConnectedAccount,
},
},
],
workspaceId,
});
const newMessageChannel = await messageChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
{},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'messageChannel',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newMessageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
after: newMessageChannel,
},
},
],
workspaceId,
});
if (isCalendarEnabled) {
const newCalendarChannel = await calendarChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
handle, handle,
visibility: provider: ConnectedAccountProvider.GOOGLE,
calendarVisibility || accessToken: input.accessToken,
CalendarChannelVisibility.SHARE_EVERYTHING, refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
}, },
{}, {},
manager, manager,
); );
const calendarChannelMetadata = const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({ await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId }, where: { nameSingular: 'connectedAccount', workspaceId },
}); });
this.workspaceEventEmitter.emitDatabaseBatchEvent({ this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel', objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED, action: DatabaseEventAction.CREATED,
events: [ events: [
{ {
recordId: newCalendarChannel.id, recordId: newConnectedAccount.id,
objectMetadata: calendarChannelMetadata, objectMetadata: connectedAccountMetadata,
properties: { properties: {
after: newCalendarChannel, after: newConnectedAccount,
}, },
}, },
], ],
workspaceId, workspaceId,
}); });
}
} else {
const updatedConnectedAccount = await connectedAccountRepository.update(
{
id: newOrExistingConnectedAccountId,
},
{
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
},
manager,
);
const connectedAccountMetadata = const newMessageChannel = await messageChannelRepository.save(
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.UPDATED,
events: [
{ {
recordId: newOrExistingConnectedAccountId, id: v4(),
objectMetadata: connectedAccountMetadata, connectedAccountId: newOrExistingConnectedAccountId,
properties: { type: MessageChannelType.EMAIL,
before: connectedAccount, handle,
after: { visibility:
...connectedAccount, messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
...updatedConnectedAccount.raw[0], syncStatus: MessageChannelSyncStatus.ONGOING,
},
},
}, },
], {},
workspaceId, manager,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
); );
const workspaceMember = await workspaceMemberRepository.findOneOrFail({ const messageChannelMetadata =
where: { id: workspaceMemberId }, await this.objectMetadataRepository.findOneOrFail({
}); where: { nameSingular: 'messageChannel', workspaceId },
});
const userId = workspaceMember.userId; this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'messageChannel',
await this.accountsToReconnectService.removeAccountToReconnect( action: DatabaseEventAction.CREATED,
userId, events: [
workspaceId, {
newOrExistingConnectedAccountId, recordId: newMessageChannel.id,
); objectMetadata: messageChannelMetadata,
properties: {
const messageChannels = await messageChannelRepository.find({ after: newMessageChannel,
where: { connectedAccountId: newOrExistingConnectedAccountId }, },
}); },
],
const messageChannelUpdates = await messageChannelRepository.update( workspaceId,
{
connectedAccountId: newOrExistingConnectedAccountId,
},
{
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
syncStatus: null,
syncCursor: '',
syncStageStartedAt: null,
},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
}); });
this.workspaceEventEmitter.emitDatabaseBatchEvent({ if (isCalendarEnabled) {
objectMetadataNameSingular: 'messageChannel', const newCalendarChannel = await calendarChannelRepository.save(
action: DatabaseEventAction.UPDATED, {
events: messageChannels.map((messageChannel) => ({ id: v4(),
recordId: messageChannel.id, connectedAccountId: newOrExistingConnectedAccountId,
objectMetadata: messageChannelMetadata, handle,
properties: { visibility:
before: messageChannel, calendarVisibility ||
after: { ...messageChannel, ...messageChannelUpdates.raw[0] }, CalendarChannelVisibility.SHARE_EVERYTHING,
},
{},
manager,
);
const calendarChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newCalendarChannel.id,
objectMetadata: calendarChannelMetadata,
properties: {
after: newCalendarChannel,
},
},
],
workspaceId,
});
}
} else {
const updatedConnectedAccount =
await connectedAccountRepository.update(
{
id: newOrExistingConnectedAccountId,
},
{
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
},
manager,
);
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.UPDATED,
events: [
{
recordId: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
before: connectedAccount,
after: {
...connectedAccount,
...updatedConnectedAccount.raw[0],
},
},
},
],
workspaceId,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMember = await workspaceMemberRepository.findOneOrFail(
{
where: { id: workspaceMemberId },
}, },
})), );
workspaceId,
}); const userId = workspaceMember.userId;
}
}); await this.accountsToReconnectService.removeAccountToReconnect(
userId,
workspaceId,
newOrExistingConnectedAccountId,
);
const messageChannels = await messageChannelRepository.find({
where: { connectedAccountId: newOrExistingConnectedAccountId },
});
const messageChannelUpdates = await messageChannelRepository.update(
{
connectedAccountId: newOrExistingConnectedAccountId,
},
{
syncStage:
MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
syncStatus: null,
syncCursor: '',
syncStageStartedAt: null,
},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'messageChannel',
action: DatabaseEventAction.UPDATED,
events: messageChannels.map((messageChannel) => ({
recordId: messageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
before: messageChannel,
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
},
})),
workspaceId,
});
}
},
);
if (this.twentyConfigService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) { if (this.twentyConfigService.get('MESSAGING_PROVIDER_GMAIL_ENABLED')) {
const messageChannels = await messageChannelRepository.find({ const messageChannels = await messageChannelRepository.find({

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { EntityManager, Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
@ -12,6 +12,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { import {
@ -107,226 +108,232 @@ export class MicrosoftAPIsService {
const scopes = getMicrosoftApisOauthScopes(); const scopes = getMicrosoftApisOauthScopes();
await workspaceDataSource.transaction(async (manager: EntityManager) => { await workspaceDataSource.transaction(
if (!existingAccountId) { async (manager: WorkspaceEntityManager) => {
const newConnectedAccount = await connectedAccountRepository.save( if (!existingAccountId) {
{ const newConnectedAccount = await connectedAccountRepository.save(
id: newOrExistingConnectedAccountId,
handle,
provider: ConnectedAccountProvider.MICROSOFT,
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
},
{},
manager,
);
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED,
events: [
{ {
recordId: newConnectedAccount.id, id: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
after: newConnectedAccount,
},
},
],
workspaceId,
});
const newMessageChannel = await messageChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
{},
manager,
);
await messageFolderRepository.save(
{
id: v4(),
messageChannelId: newMessageChannel.id,
name: MessageFolderName.INBOX,
syncCursor: '',
},
{},
manager,
);
await messageFolderRepository.save(
{
id: v4(),
messageChannelId: newMessageChannel.id,
name: MessageFolderName.SENT_ITEMS,
syncCursor: '',
},
{},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'messageChannel',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newMessageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
after: newMessageChannel,
},
},
],
workspaceId,
});
if (
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
) {
const newCalendarChannel = await calendarChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
handle, handle,
visibility: provider: ConnectedAccountProvider.MICROSOFT,
calendarVisibility || accessToken: input.accessToken,
CalendarChannelVisibility.SHARE_EVERYTHING, refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
}, },
{}, {},
manager, manager,
); );
const calendarChannelMetadata = const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({ await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId }, where: { nameSingular: 'connectedAccount', workspaceId },
}); });
this.workspaceEventEmitter.emitDatabaseBatchEvent({ this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel', objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED, action: DatabaseEventAction.CREATED,
events: [ events: [
{ {
recordId: newCalendarChannel.id, recordId: newConnectedAccount.id,
objectMetadata: calendarChannelMetadata, objectMetadata: connectedAccountMetadata,
properties: { properties: {
after: newCalendarChannel, after: newConnectedAccount,
}, },
}, },
], ],
workspaceId, workspaceId,
}); });
}
} else {
const updatedConnectedAccount = await connectedAccountRepository.update(
{
id: newOrExistingConnectedAccountId,
},
{
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
},
manager,
);
const connectedAccountMetadata = const newMessageChannel = await messageChannelRepository.save(
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.UPDATED,
events: [
{ {
recordId: newOrExistingConnectedAccountId, id: v4(),
objectMetadata: connectedAccountMetadata, connectedAccountId: newOrExistingConnectedAccountId,
properties: { type: MessageChannelType.EMAIL,
before: connectedAccount, handle,
after: { visibility:
...connectedAccount, messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
...updatedConnectedAccount.raw[0], syncStatus: MessageChannelSyncStatus.ONGOING,
},
},
}, },
], {},
workspaceId, manager,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
); );
const workspaceMember = await workspaceMemberRepository.findOneOrFail({ await messageFolderRepository.save(
where: { id: workspaceMemberId }, {
}); id: v4(),
messageChannelId: newMessageChannel.id,
name: MessageFolderName.INBOX,
syncCursor: '',
},
{},
manager,
);
const userId = workspaceMember.userId; await messageFolderRepository.save(
{
id: v4(),
messageChannelId: newMessageChannel.id,
name: MessageFolderName.SENT_ITEMS,
syncCursor: '',
},
{},
manager,
);
await this.accountsToReconnectService.removeAccountToReconnect( const messageChannelMetadata =
userId, await this.objectMetadataRepository.findOneOrFail({
workspaceId, where: { nameSingular: 'messageChannel', workspaceId },
newOrExistingConnectedAccountId, });
);
const messageChannels = await messageChannelRepository.find({ this.workspaceEventEmitter.emitDatabaseBatchEvent({
where: { connectedAccountId: newOrExistingConnectedAccountId }, objectMetadataNameSingular: 'messageChannel',
}); action: DatabaseEventAction.CREATED,
events: [
const messageChannelUpdates = await messageChannelRepository.update( {
{ recordId: newMessageChannel.id,
connectedAccountId: newOrExistingConnectedAccountId, objectMetadata: messageChannelMetadata,
}, properties: {
{ after: newMessageChannel,
syncStage: MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING, },
syncStatus: MessageChannelSyncStatus.ONGOING, },
syncCursor: '', ],
syncStageStartedAt: null, workspaceId,
},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
}); });
this.workspaceEventEmitter.emitDatabaseBatchEvent({ if (
objectMetadataNameSingular: 'messageChannel', this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
action: DatabaseEventAction.UPDATED, ) {
events: messageChannels.map((messageChannel) => ({ const newCalendarChannel = await calendarChannelRepository.save(
recordId: messageChannel.id, {
objectMetadata: messageChannelMetadata, id: v4(),
properties: { connectedAccountId: newOrExistingConnectedAccountId,
before: messageChannel, handle,
after: { ...messageChannel, ...messageChannelUpdates.raw[0] }, visibility:
calendarVisibility ||
CalendarChannelVisibility.SHARE_EVERYTHING,
},
{},
manager,
);
const calendarChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newCalendarChannel.id,
objectMetadata: calendarChannelMetadata,
properties: {
after: newCalendarChannel,
},
},
],
workspaceId,
});
}
} else {
const updatedConnectedAccount =
await connectedAccountRepository.update(
{
id: newOrExistingConnectedAccountId,
},
{
accessToken: input.accessToken,
refreshToken: input.refreshToken,
scopes,
},
manager,
);
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.UPDATED,
events: [
{
recordId: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
before: connectedAccount,
after: {
...connectedAccount,
...updatedConnectedAccount.raw[0],
},
},
},
],
workspaceId,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
);
const workspaceMember = await workspaceMemberRepository.findOneOrFail(
{
where: { id: workspaceMemberId },
}, },
})), );
workspaceId,
}); const userId = workspaceMember.userId;
}
}); await this.accountsToReconnectService.removeAccountToReconnect(
userId,
workspaceId,
newOrExistingConnectedAccountId,
);
const messageChannels = await messageChannelRepository.find({
where: { connectedAccountId: newOrExistingConnectedAccountId },
});
const messageChannelUpdates = await messageChannelRepository.update(
{
connectedAccountId: newOrExistingConnectedAccountId,
},
{
syncStage:
MessageChannelSyncStage.FULL_MESSAGE_LIST_FETCH_PENDING,
syncStatus: MessageChannelSyncStatus.ONGOING,
syncCursor: '',
syncStageStartedAt: null,
},
manager,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'messageChannel',
action: DatabaseEventAction.UPDATED,
events: messageChannels.map((messageChannel) => ({
recordId: messageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
before: messageChannel,
after: { ...messageChannel, ...messageChannelUpdates.raw[0] },
},
})),
workspaceId,
});
}
},
);
if (this.twentyConfigService.get('MESSAGING_PROVIDER_MICROSOFT_ENABLED')) { if (this.twentyConfigService.get('MESSAGING_PROVIDER_MICROSOFT_ENABLED')) {
const messageChannels = await messageChannelRepository.find({ const messageChannels = await messageChannelRepository.find({

View File

@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager, In } from 'typeorm'; import { In } from 'typeorm';
import { import {
FieldMetadataComplexOption, FieldMetadataComplexOption,
@ -28,7 +28,6 @@ export class FieldMetadataRelatedRecordsService {
public async updateRelatedViewGroups( public async updateRelatedViewGroups(
oldFieldMetadata: FieldMetadataEntity, oldFieldMetadata: FieldMetadataEntity,
newFieldMetadata: FieldMetadataEntity, newFieldMetadata: FieldMetadataEntity,
transactionManager?: EntityManager,
): Promise<void> { ): Promise<void> {
if ( if (
!isSelectFieldMetadataType(newFieldMetadata.type) || !isSelectFieldMetadataType(newFieldMetadata.type) ||
@ -67,7 +66,7 @@ export class FieldMetadataRelatedRecordsService {
}), }),
); );
await viewGroupRepository.insert(viewGroupsToCreate, transactionManager); await viewGroupRepository.insert(viewGroupsToCreate);
for (const { old: oldOption, new: newOption } of updated) { for (const { old: oldOption, new: newOption } of updated) {
const existingViewGroup = view.viewGroups.find( const existingViewGroup = view.viewGroups.find(
@ -83,25 +82,20 @@ export class FieldMetadataRelatedRecordsService {
await viewGroupRepository.update( await viewGroupRepository.update(
{ id: existingViewGroup.id }, { id: existingViewGroup.id },
{ fieldValue: newOption.value }, { fieldValue: newOption.value },
transactionManager,
); );
} }
const valuesToDelete = deleted.map((option) => option.value); const valuesToDelete = deleted.map((option) => option.value);
await viewGroupRepository.delete( await viewGroupRepository.delete({
{ fieldMetadataId: newFieldMetadata.id,
fieldMetadataId: newFieldMetadata.id, fieldValue: In(valuesToDelete),
fieldValue: In(valuesToDelete), });
},
transactionManager,
);
await this.syncNoValueViewGroup( await this.syncNoValueViewGroup(
newFieldMetadata, newFieldMetadata,
view, view,
viewGroupRepository, viewGroupRepository,
transactionManager,
); );
} }
} }
@ -110,7 +104,6 @@ export class FieldMetadataRelatedRecordsService {
fieldMetadata: FieldMetadataEntity, fieldMetadata: FieldMetadataEntity,
view: ViewWorkspaceEntity, view: ViewWorkspaceEntity,
viewGroupRepository: WorkspaceRepository<ViewGroupWorkspaceEntity>, viewGroupRepository: WorkspaceRepository<ViewGroupWorkspaceEntity>,
transactionManager?: EntityManager,
): Promise<void> { ): Promise<void> {
const noValueGroup = view.viewGroups.find( const noValueGroup = view.viewGroups.find(
(group) => group.fieldValue === '', (group) => group.fieldValue === '',
@ -126,12 +119,9 @@ export class FieldMetadataRelatedRecordsService {
viewId: view.id, viewId: view.id,
}); });
await viewGroupRepository.insert(newGroup, transactionManager); await viewGroupRepository.insert(newGroup);
} else if (!fieldMetadata.isNullable && noValueGroup) { } else if (!fieldMetadata.isNullable && noValueGroup) {
await viewGroupRepository.delete( await viewGroupRepository.delete({ id: noValueGroup.id });
{ id: noValueGroup.id },
transactionManager,
);
} }
} }

View File

@ -26,6 +26,7 @@ import {
validateStringAgainstInjections, validateStringAgainstInjections,
} from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils'; } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils';
import { validateRemoteServerType } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util'; import { validateRemoteServerType } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable() @Injectable()
@ -79,7 +80,7 @@ export class RemoteServerService<T extends RemoteServerType> {
} }
return this.metadataDataSource.transaction( return this.metadataDataSource.transaction(
async (entityManager: EntityManager) => { async (entityManager: WorkspaceEntityManager) => {
const createdRemoteServer = entityManager.create( const createdRemoteServer = entityManager.create(
RemoteServerEntity, RemoteServerEntity,
remoteServerToCreate, remoteServerToCreate,

View File

@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { FieldMetadataType } from 'twenty-shared/types'; import { FieldMetadataType } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { DataSource, EntityManager } from 'typeorm';
import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed'; import { ObjectMetadataSeed } from 'src/engine/seeder/interfaces/object-metadata-seed';
@ -60,12 +61,13 @@ export class SeederService {
const schemaName = const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource = const workspaceDataSource: DataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource( await this.workspaceDataSourceService.connectToWorkspaceDataSource(
workspaceId, workspaceId,
); );
const entityManager = workspaceDataSource.createEntityManager(); const entityManager: EntityManager =
workspaceDataSource.createEntityManager();
const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter( const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter(
(field) => (field) =>

View File

@ -5,12 +5,14 @@ import {
EntityTarget, EntityTarget,
ObjectLiteral, ObjectLiteral,
QueryRunner, QueryRunner,
ReplicationMode,
} from 'typeorm'; } from 'typeorm';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface'; import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/entity.manager'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { WorkspaceQueryRunner } from 'src/engine/twenty-orm/query-runner/workspace-query-runner';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export class WorkspaceDataSource extends DataSource { export class WorkspaceDataSource extends DataSource {
@ -31,10 +33,10 @@ export class WorkspaceDataSource extends DataSource {
) { ) {
super(options); super(options);
this.internalContext = internalContext; this.internalContext = internalContext;
// Recreate manager after internalContext has been initialized
this.manager = this.createEntityManager();
this.featureFlagMap = featureFlagMap; this.featureFlagMap = featureFlagMap;
this.featureFlagMapVersion = featureFlagMapVersion; this.featureFlagMapVersion = featureFlagMapVersion;
// Recreate manager after internalContext has been initialized
this.manager = this.createEntityManager();
this.rolesPermissionsVersion = rolesPermissionsVersion; this.rolesPermissionsVersion = rolesPermissionsVersion;
this.permissionsPerRoleId = permissionsPerRoleId; this.permissionsPerRoleId = permissionsPerRoleId;
} }
@ -65,6 +67,17 @@ export class WorkspaceDataSource extends DataSource {
return new WorkspaceEntityManager(this.internalContext, this, queryRunner); return new WorkspaceEntityManager(this.internalContext, this, queryRunner);
} }
override createQueryRunner(
mode = 'master' as ReplicationMode,
): WorkspaceQueryRunner {
const queryRunner = this.driver.createQueryRunner(mode);
const manager = this.createEntityManager(queryRunner);
Object.assign(queryRunner, { manager: manager });
return queryRunner as any as WorkspaceQueryRunner;
}
setRolesPermissionsVersion(rolesPermissionsVersion: string) { setRolesPermissionsVersion(rolesPermissionsVersion: string) {
this.rolesPermissionsVersion = rolesPermissionsVersion; this.rolesPermissionsVersion = rolesPermissionsVersion;
} }

View File

@ -1,95 +0,0 @@
import {
DataSource,
EntityManager,
EntityTarget,
ObjectLiteral,
QueryRunner,
Repository,
} from 'typeorm';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export class WorkspaceEntityManager extends EntityManager {
private readonly internalContext: WorkspaceInternalContext;
readonly repositories: Map<string, Repository<any>>;
constructor(
internalContext: WorkspaceInternalContext,
connection: DataSource,
queryRunner?: QueryRunner,
) {
super(connection, queryRunner);
this.internalContext = internalContext;
this.repositories = new Map();
}
override getRepository<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
shouldBypassPermissionChecks = false,
roleId?: string,
): WorkspaceRepository<Entity> {
const dataSource = this.connection as WorkspaceDataSource;
const repositoryKey = this.getRepositoryKey({
target,
dataSource,
roleId,
shouldBypassPermissionChecks,
});
const repoFromMap = this.repositories.get(repositoryKey);
if (repoFromMap) {
return repoFromMap as WorkspaceRepository<Entity>;
}
let objectPermissions = {};
if (roleId) {
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
}
const newRepository = new WorkspaceRepository<Entity>(
this.internalContext,
target,
this,
dataSource.featureFlagMap,
this.queryRunner,
objectPermissions,
shouldBypassPermissionChecks,
);
this.repositories.set(repositoryKey, newRepository);
return newRepository;
}
private getRepositoryKey({
target,
dataSource,
roleId,
shouldBypassPermissionChecks,
}: {
target: EntityTarget<any>;
dataSource: WorkspaceDataSource;
shouldBypassPermissionChecks: boolean;
roleId?: string;
}) {
const repositoryPrefix = dataSource.getMetadata(target).name;
const roleIdSuffix = roleId ? `_${roleId}` : '';
const rolesPermissionsVersionSuffix = dataSource.rolesPermissionsVersion
? `_${dataSource.rolesPermissionsVersion}`
: '';
const featureFlagMapVersionSuffix = dataSource.featureFlagMapVersion
? `_${dataSource.featureFlagMapVersion}`
: '';
return shouldBypassPermissionChecks
? `${repositoryPrefix}_bypass${featureFlagMapVersionSuffix}`
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
}
}

View File

@ -0,0 +1,228 @@
import { ObjectRecordsPermissions } from 'twenty-shared/types';
import {
EntityManager,
EntityTarget,
InsertResult,
ObjectLiteral,
QueryRunner,
Repository,
SelectQueryBuilder,
} from 'typeorm';
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
import {
OperationType,
validateOperationIsPermittedOrThrow,
} from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
export class WorkspaceEntityManager extends EntityManager {
private readonly internalContext: WorkspaceInternalContext;
readonly repositories: Map<string, Repository<any>>;
declare connection: WorkspaceDataSource;
constructor(
internalContext: WorkspaceInternalContext,
connection: WorkspaceDataSource,
queryRunner?: QueryRunner,
) {
super(connection, queryRunner);
this.internalContext = internalContext;
this.repositories = new Map();
}
getFeatureFlagMap(): FeatureFlagMap {
return this.connection.featureFlagMap;
}
override getRepository<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
shouldBypassPermissionChecks = false,
roleId?: string,
): WorkspaceRepository<Entity> {
const dataSource = this.connection;
const repositoryKey = this.getRepositoryKey({
target,
dataSource,
roleId,
shouldBypassPermissionChecks,
});
const repoFromMap = this.repositories.get(repositoryKey);
if (repoFromMap) {
return repoFromMap as WorkspaceRepository<Entity>;
}
let objectPermissions = {};
if (roleId) {
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
objectPermissions = objectPermissionsByRoleId?.[roleId] ?? {};
}
const newRepository = new WorkspaceRepository<Entity>(
this.internalContext,
target,
this,
dataSource.featureFlagMap,
this.queryRunner,
objectPermissions,
shouldBypassPermissionChecks,
);
this.repositories.set(repositoryKey, newRepository);
return newRepository;
}
override createQueryBuilder<Entity extends ObjectLiteral>(
entityClassOrQueryRunner?: EntityTarget<Entity> | QueryRunner,
alias?: string,
queryRunner?: QueryRunner,
options: {
shouldBypassPermissionChecks: boolean;
roleId?: string;
} = {
shouldBypassPermissionChecks: false,
},
): SelectQueryBuilder<Entity> | WorkspaceSelectQueryBuilder<Entity> {
let queryBuilder: SelectQueryBuilder<Entity>;
if (alias) {
queryBuilder = super.createQueryBuilder(
entityClassOrQueryRunner as EntityTarget<Entity>,
alias as string,
queryRunner as QueryRunner | undefined,
);
} else {
queryBuilder = super.createQueryBuilder(
entityClassOrQueryRunner as QueryRunner,
);
}
const featureFlagMap = this.getFeatureFlagMap();
const isPermissionsV2Enabled =
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
if (!isPermissionsV2Enabled) {
return queryBuilder;
} else {
let objectPermissions = {};
if (options?.roleId) {
const dataSource = this.connection as WorkspaceDataSource;
const objectPermissionsByRoleId = dataSource.permissionsPerRoleId;
objectPermissions = objectPermissionsByRoleId?.[options.roleId] ?? {};
}
return new WorkspaceSelectQueryBuilder(
queryBuilder,
objectPermissions,
this.internalContext,
options?.shouldBypassPermissionChecks ?? false,
);
}
}
override insert<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
entityOrEntities:
| QueryDeepPartialEntity<Entity>
| QueryDeepPartialEntity<Entity>[],
options?: {
shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions;
},
): Promise<InsertResult> {
this.validatePermissions(target, 'insert', options);
return super.insert(target, entityOrEntities);
}
override upsert<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
entityOrEntities:
| QueryDeepPartialEntity<Entity>
| QueryDeepPartialEntity<Entity>[],
conflictPathsOrOptions: string[] | UpsertOptions<Entity>,
options?: {
shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions;
},
): Promise<InsertResult> {
this.validatePermissions(target, 'update', options);
return super.upsert(target, entityOrEntities, conflictPathsOrOptions);
}
private getRepositoryKey({
target,
dataSource,
roleId,
shouldBypassPermissionChecks,
}: {
target: EntityTarget<any>;
dataSource: WorkspaceDataSource;
shouldBypassPermissionChecks: boolean;
roleId?: string;
}) {
const repositoryPrefix = dataSource.getMetadata(target).name;
const roleIdSuffix = roleId ? `_${roleId}` : '';
const rolesPermissionsVersionSuffix = dataSource.rolesPermissionsVersion
? `_${dataSource.rolesPermissionsVersion}`
: '';
const featureFlagMapVersionSuffix = dataSource.featureFlagMapVersion
? `_${dataSource.featureFlagMapVersion}`
: '';
return shouldBypassPermissionChecks
? `${repositoryPrefix}_bypass${featureFlagMapVersionSuffix}`
: `${repositoryPrefix}${roleIdSuffix}${rolesPermissionsVersionSuffix}${featureFlagMapVersionSuffix}`;
}
private validatePermissions<Entity extends ObjectLiteral>(
target: EntityTarget<Entity>,
operationType: OperationType,
options?: {
shouldBypassPermissionChecks?: boolean;
objectRecordsPermissions?: ObjectRecordsPermissions;
},
): void {
const featureFlagMap = this.getFeatureFlagMap();
const isPermissionsV2Enabled =
featureFlagMap[FeatureFlagKey.IsPermissionsV2Enabled];
if (!isPermissionsV2Enabled) {
return;
}
if (options?.shouldBypassPermissionChecks === true) {
return;
}
validateOperationIsPermittedOrThrow({
entityName: this.extractTargetNameSingularFromEntityTarget(target),
operationType,
objectRecordsPermissions: options?.objectRecordsPermissions ?? {},
objectMetadataMaps: this.internalContext.objectMetadataMaps,
});
}
private extractTargetNameSingularFromEntityTarget(
target: EntityTarget<any>,
): string {
return this.connection.getMetadata(target).name;
}
}

View File

@ -0,0 +1,9 @@
import { QueryRunner } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
interface WorkspaceQueryRunner extends Omit<QueryRunner, 'manager'> {
manager: WorkspaceEntityManager;
}
export { WorkspaceQueryRunner };

View File

@ -18,21 +18,27 @@ const getTargetEntityAndOperationType = (expressionMap: QueryExpressionMap) => {
}; };
}; };
export const validateQueryIsPermittedOrThrow = ( export type OperationType =
expressionMap: QueryExpressionMap, | 'select'
objectRecordsPermissions: ObjectRecordsPermissions, | 'insert'
objectMetadataMaps: ObjectMetadataMaps, | 'update'
shouldBypassPermissionChecks: boolean, | 'delete'
) => { | 'restore'
if (shouldBypassPermissionChecks) { | 'soft-delete';
return;
}
const { mainEntity, operationType } =
getTargetEntityAndOperationType(expressionMap);
export const validateOperationIsPermittedOrThrow = ({
entityName,
operationType,
objectRecordsPermissions,
objectMetadataMaps,
}: {
entityName: string;
operationType: OperationType;
objectRecordsPermissions: ObjectRecordsPermissions;
objectMetadataMaps: ObjectMetadataMaps;
}) => {
const objectMetadataIdForEntity = const objectMetadataIdForEntity =
objectMetadataMaps.idByNameSingular[mainEntity]; objectMetadataMaps.idByNameSingular[entityName];
const objectMetadataIsSystem = const objectMetadataIsSystem =
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true; objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
@ -41,7 +47,7 @@ export const validateQueryIsPermittedOrThrow = (
return; return;
} }
const permissionsForEntity = objectRecordsPermissions[mainEntity]; const permissionsForEntity = objectRecordsPermissions[entityName];
switch (operationType) { switch (operationType) {
case 'select': case 'select':
@ -85,3 +91,24 @@ export const validateQueryIsPermittedOrThrow = (
); );
} }
}; };
export const validateQueryIsPermittedOrThrow = (
expressionMap: QueryExpressionMap,
objectRecordsPermissions: ObjectRecordsPermissions,
objectMetadataMaps: ObjectMetadataMaps,
shouldBypassPermissionChecks: boolean,
) => {
if (shouldBypassPermissionChecks) {
return;
}
const { mainEntity, operationType } =
getTargetEntityAndOperationType(expressionMap);
validateOperationIsPermittedOrThrow({
entityName: mainEntity,
operationType: operationType as OperationType,
objectRecordsPermissions,
objectMetadataMaps,
});
};

View File

@ -9,7 +9,7 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';

View File

@ -3,7 +3,7 @@ import { InsertQueryBuilder, ObjectLiteral } from 'typeorm';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';

View File

@ -4,7 +4,7 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
@ -50,6 +50,48 @@ export class WorkspaceSelectQueryBuilder<
return super.getMany(); return super.getMany();
} }
override getRawOne<U = any>(): Promise<U | undefined> {
this.validatePermissions();
return super.getRawOne();
}
override getRawMany<U = any>(): Promise<U[]> {
this.validatePermissions();
return super.getRawMany();
}
override getOne(): Promise<T | null> {
this.validatePermissions();
return super.getOne();
}
override getOneOrFail(): Promise<T> {
this.validatePermissions();
return super.getOneOrFail();
}
override getCount(): Promise<number> {
this.validatePermissions();
return super.getCount();
}
override getExists(): Promise<boolean> {
this.validatePermissions();
return super.getExists();
}
override getManyAndCount(): Promise<[T[], number]> {
this.validatePermissions();
return super.getManyAndCount();
}
override update(): WorkspaceUpdateQueryBuilder<T>; override update(): WorkspaceUpdateQueryBuilder<T>;
override update( override update(

View File

@ -4,7 +4,7 @@ import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBui
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder'; import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';

View File

@ -3,7 +3,7 @@ import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface'; import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.util'; import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder'; import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder'; import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';

View File

@ -2,7 +2,6 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
import { import {
DeepPartial, DeepPartial,
DeleteResult, DeleteResult,
EntityManager,
EntitySchema, EntitySchema,
EntityTarget, EntityTarget,
FindManyOptions, FindManyOptions,
@ -27,6 +26,7 @@ import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/works
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util'; import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder'; import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage'; import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspace-entities.storage';
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util'; import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
@ -39,10 +39,12 @@ export class WorkspaceRepository<
private shouldBypassPermissionChecks: boolean; private shouldBypassPermissionChecks: boolean;
private featureFlagMap: FeatureFlagMap; private featureFlagMap: FeatureFlagMap;
private objectRecordsPermissions?: ObjectRecordsPermissions; private objectRecordsPermissions?: ObjectRecordsPermissions;
declare manager: WorkspaceEntityManager;
constructor( constructor(
internalContext: WorkspaceInternalContext, internalContext: WorkspaceInternalContext,
target: EntityTarget<T>, target: EntityTarget<T>,
manager: EntityManager, manager: WorkspaceEntityManager,
featureFlagMap: FeatureFlagMap, featureFlagMap: FeatureFlagMap,
queryRunner?: QueryRunner, queryRunner?: QueryRunner,
objectRecordsPermissions?: ObjectRecordsPermissions, objectRecordsPermissions?: ObjectRecordsPermissions,
@ -53,6 +55,7 @@ export class WorkspaceRepository<
this.featureFlagMap = featureFlagMap; this.featureFlagMap = featureFlagMap;
this.objectRecordsPermissions = objectRecordsPermissions; this.objectRecordsPermissions = objectRecordsPermissions;
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks; this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
this.manager = manager;
} }
override createQueryBuilder<U extends T>( override createQueryBuilder<U extends T>(
@ -87,7 +90,7 @@ export class WorkspaceRepository<
*/ */
override async find( override async find(
options?: FindManyOptions<T>, options?: FindManyOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T[]> { ): Promise<T[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -99,7 +102,7 @@ export class WorkspaceRepository<
override async findBy( override async findBy(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T[]> { ): Promise<T[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -111,7 +114,7 @@ export class WorkspaceRepository<
override async findAndCount( override async findAndCount(
options?: FindManyOptions<T>, options?: FindManyOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<[T[], number]> { ): Promise<[T[], number]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -123,7 +126,7 @@ export class WorkspaceRepository<
override async findAndCountBy( override async findAndCountBy(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<[T[], number]> { ): Promise<[T[], number]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -138,7 +141,7 @@ export class WorkspaceRepository<
override async findOne( override async findOne(
options: FindOneOptions<T>, options: FindOneOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T | null> { ): Promise<T | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -150,7 +153,7 @@ export class WorkspaceRepository<
override async findOneBy( override async findOneBy(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T | null> { ): Promise<T | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -162,7 +165,7 @@ export class WorkspaceRepository<
override async findOneOrFail( override async findOneOrFail(
options: FindOneOptions<T>, options: FindOneOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T> { ): Promise<T> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -174,7 +177,7 @@ export class WorkspaceRepository<
override async findOneByOrFail( override async findOneByOrFail(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T> { ): Promise<T> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -193,31 +196,31 @@ export class WorkspaceRepository<
override save<U extends DeepPartial<T>>( override save<U extends DeepPartial<T>>(
entities: U[], entities: U[],
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T[]>; ): Promise<T[]>;
override save<U extends DeepPartial<T>>( override save<U extends DeepPartial<T>>(
entities: U[], entities: U[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<(U & T)[]>; ): Promise<(U & T)[]>;
override save<U extends DeepPartial<T>>( override save<U extends DeepPartial<T>>(
entity: U, entity: U,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T>; ): Promise<T>;
override save<U extends DeepPartial<T>>( override save<U extends DeepPartial<T>>(
entity: U, entity: U,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U & T>; ): Promise<U & T>;
override async save<U extends DeepPartial<T>>( override async save<U extends DeepPartial<T>>(
entityOrEntities: U | U[], entityOrEntities: U | U[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U | U[]> { ): Promise<U | U[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formattedEntityOrEntities = await this.formatData(entityOrEntities); const formattedEntityOrEntities = await this.formatData(entityOrEntities);
@ -249,19 +252,19 @@ export class WorkspaceRepository<
override remove( override remove(
entities: T[], entities: T[],
options?: RemoveOptions, options?: RemoveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T[]>; ): Promise<T[]>;
override remove( override remove(
entity: T, entity: T,
options?: RemoveOptions, options?: RemoveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T>; ): Promise<T>;
override async remove( override async remove(
entityOrEntities: T | T[], entityOrEntities: T | T[],
options?: RemoveOptions, options?: RemoveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T | T[]> { ): Promise<T | T[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formattedEntityOrEntities = await this.formatData(entityOrEntities); const formattedEntityOrEntities = await this.formatData(entityOrEntities);
@ -287,7 +290,7 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<T>, | FindOptionsWhere<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<DeleteResult> { ): Promise<DeleteResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
@ -301,31 +304,31 @@ export class WorkspaceRepository<
override softRemove<U extends DeepPartial<T>>( override softRemove<U extends DeepPartial<T>>(
entities: U[], entities: U[],
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<T[]>; ): Promise<T[]>;
override softRemove<U extends DeepPartial<T>>( override softRemove<U extends DeepPartial<T>>(
entities: U[], entities: U[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<(U & T)[]>; ): Promise<(U & T)[]>;
override softRemove<U extends DeepPartial<T>>( override softRemove<U extends DeepPartial<T>>(
entity: U, entity: U,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U>; ): Promise<U>;
override softRemove<U extends DeepPartial<T>>( override softRemove<U extends DeepPartial<T>>(
entity: T, entity: T,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U & T>; ): Promise<U & T>;
override async softRemove<U extends DeepPartial<T>>( override async softRemove<U extends DeepPartial<T>>(
entityOrEntities: U | U[], entityOrEntities: U | U[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U | U[]> { ): Promise<U | U[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formattedEntityOrEntities = await this.formatData(entityOrEntities); const formattedEntityOrEntities = await this.formatData(entityOrEntities);
@ -362,7 +365,7 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<T>, | FindOptionsWhere<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
@ -379,31 +382,31 @@ export class WorkspaceRepository<
override recover<U extends DeepPartial<T>>( override recover<U extends DeepPartial<T>>(
entities: U, entities: U,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U>; ): Promise<U>;
override recover<U extends DeepPartial<T>>( override recover<U extends DeepPartial<T>>(
entities: U, entities: U,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<(U & T)[]>; ): Promise<(U & T)[]>;
override recover<U extends DeepPartial<T>>( override recover<U extends DeepPartial<T>>(
entity: U, entity: U,
options: SaveOptions & { reload: false }, options: SaveOptions & { reload: false },
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U>; ): Promise<U>;
override recover<U extends DeepPartial<T>>( override recover<U extends DeepPartial<T>>(
entity: U, entity: U,
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U & T>; ): Promise<U & T>;
override async recover<U extends DeepPartial<T>>( override async recover<U extends DeepPartial<T>>(
entityOrEntities: U | U[], entityOrEntities: U | U[],
options?: SaveOptions, options?: SaveOptions,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<U | U[]> { ): Promise<U | U[]> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formattedEntityOrEntities = await this.formatData(entityOrEntities); const formattedEntityOrEntities = await this.formatData(entityOrEntities);
@ -440,7 +443,7 @@ export class WorkspaceRepository<
| ObjectId | ObjectId
| ObjectId[] | ObjectId[]
| FindOptionsWhere<T>, | FindOptionsWhere<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
@ -456,12 +459,15 @@ export class WorkspaceRepository<
*/ */
override async insert( override async insert(
entity: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[], entity: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<InsertResult> { ): Promise<InsertResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const formatedEntity = await this.formatData(entity); const formattedEntity = await this.formatData(entity);
const result = await manager.insert(this.target, formatedEntity); const result = await manager.insert(this.target, formattedEntity, {
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
objectRecordsPermissions: this.objectRecordsPermissions,
});
const formattedResult = await this.formatResult(result.generatedMaps); const formattedResult = await this.formatResult(result.generatedMaps);
return { return {
@ -486,7 +492,7 @@ export class WorkspaceRepository<
| ObjectId[] | ObjectId[]
| FindOptionsWhere<T>, | FindOptionsWhere<T>,
partialEntity: QueryDeepPartialEntity<T>, partialEntity: QueryDeepPartialEntity<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
@ -500,7 +506,7 @@ export class WorkspaceRepository<
override async upsert( override async upsert(
entityOrEntities: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[], entityOrEntities: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
conflictPathsOrOptions: string[] | UpsertOptions<T>, conflictPathsOrOptions: string[] | UpsertOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<InsertResult> { ): Promise<InsertResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
@ -510,6 +516,10 @@ export class WorkspaceRepository<
this.target, this.target,
formattedEntityOrEntities, formattedEntityOrEntities,
conflictPathsOrOptions, conflictPathsOrOptions,
{
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
objectRecordsPermissions: this.objectRecordsPermissions,
},
); );
const formattedResult = await this.formatResult(result.generatedMaps); const formattedResult = await this.formatResult(result.generatedMaps);
@ -526,7 +536,7 @@ export class WorkspaceRepository<
*/ */
override async exists( override async exists(
options?: FindManyOptions<T>, options?: FindManyOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<boolean> { ): Promise<boolean> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -536,7 +546,7 @@ export class WorkspaceRepository<
override async existsBy( override async existsBy(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<boolean> { ): Promise<boolean> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -549,7 +559,7 @@ export class WorkspaceRepository<
*/ */
override async count( override async count(
options?: FindManyOptions<T>, options?: FindManyOptions<T>,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number> { ): Promise<number> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions(options); const computedOptions = await this.transformOptions(options);
@ -559,7 +569,7 @@ export class WorkspaceRepository<
override async countBy( override async countBy(
where: FindOptionsWhere<T> | FindOptionsWhere<T>[], where: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number> { ): Promise<number> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -573,7 +583,7 @@ export class WorkspaceRepository<
override async sum( override async sum(
columnName: PickKeysByType<T, number>, columnName: PickKeysByType<T, number>,
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[], where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -584,7 +594,7 @@ export class WorkspaceRepository<
override async average( override async average(
columnName: PickKeysByType<T, number>, columnName: PickKeysByType<T, number>,
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[], where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -595,7 +605,7 @@ export class WorkspaceRepository<
override async minimum( override async minimum(
columnName: PickKeysByType<T, number>, columnName: PickKeysByType<T, number>,
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[], where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -606,7 +616,7 @@ export class WorkspaceRepository<
override async maximum( override async maximum(
columnName: PickKeysByType<T, number>, columnName: PickKeysByType<T, number>,
where?: FindOptionsWhere<T> | FindOptionsWhere<T>[], where?: FindOptionsWhere<T> | FindOptionsWhere<T>[],
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<number | null> { ): Promise<number | null> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedOptions = await this.transformOptions({ where }); const computedOptions = await this.transformOptions({ where });
@ -618,7 +628,7 @@ export class WorkspaceRepository<
conditions: FindOptionsWhere<T>, conditions: FindOptionsWhere<T>,
propertyPath: string, propertyPath: string,
value: number | string, value: number | string,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedConditions = await this.transformOptions({ const computedConditions = await this.transformOptions({
@ -637,7 +647,7 @@ export class WorkspaceRepository<
conditions: FindOptionsWhere<T>, conditions: FindOptionsWhere<T>,
propertyPath: string, propertyPath: string,
value: number | string, value: number | string,
entityManager?: EntityManager, entityManager?: WorkspaceEntityManager,
): Promise<UpdateResult> { ): Promise<UpdateResult> {
const manager = entityManager || this.manager; const manager = entityManager || this.manager;
const computedConditions = await this.transformOptions({ const computedConditions = await this.transformOptions({

View File

@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
import { DataSource, EntityManager } from 'typeorm'; import { DataSource, EntityManager } from 'typeorm';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { TypeORMService } from 'src/database/typeorm/typeorm.service'; import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@Injectable() @Injectable()
export class WorkspaceDataSourceService { export class WorkspaceDataSourceService {

View File

@ -1,13 +1,15 @@
import { EntityManager } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { COMPANIES_DEMO } from 'src/engine/workspace-manager/demo-objects-prefill-data/companies-demo.json'; import { COMPANIES_DEMO } from 'src/engine/workspace-manager/demo-objects-prefill-data/companies-demo.json';
export const seedCompanyWithDemoData = async ( export const seedCompanyWithDemoData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.company`, [ .into(`${schemaName}.company`, [
'name', 'name',

View File

@ -1,5 +1,5 @@
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data'; import { DEMO_SEED_WORKSPACE_MEMBER_IDS } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-workspace-member-with-demo-data';
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
const tableName = 'opportunity'; const tableName = 'opportunity';
@ -33,7 +33,7 @@ const generateOpportunities = (companies) => {
}; };
export const seedOpportunityWithDemoData = async ( export const seedOpportunityWithDemoData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
const companiesWithPeople = await entityManager?.query( const companiesWithPeople = await entityManager?.query(
@ -46,7 +46,9 @@ export const seedOpportunityWithDemoData = async (
const opportunities = generateOpportunities(companiesWithPeople); const opportunities = generateOpportunities(companiesWithPeople);
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.${tableName}`, [ .into(`${schemaName}.${tableName}`, [
'id', 'id',

View File

@ -1,9 +1,9 @@
import { EntityManager } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { peopleDemo } from 'src/engine/workspace-manager/demo-objects-prefill-data/people-demo.json'; import { peopleDemo } from 'src/engine/workspace-manager/demo-objects-prefill-data/people-demo.json';
export const seedPersonWithDemoData = async ( export const seedPersonWithDemoData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
const companies = await entityManager?.query( const companies = await entityManager?.query(
@ -27,7 +27,9 @@ export const seedPersonWithDemoData = async (
})); }));
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.person`, [ .into(`${schemaName}.person`, [
'nameFirstName', 'nameFirstName',

View File

@ -1,6 +1,6 @@
import { EntityManager } from 'typeorm';
import { DEMO_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/demo/users'; import { DEMO_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/demo/users';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { SOURCE_LOCALE } from 'twenty-shared/translations';
export const DEMO_SEED_WORKSPACE_MEMBER_IDS = { export const DEMO_SEED_WORKSPACE_MEMBER_IDS = {
@ -10,11 +10,13 @@ export const DEMO_SEED_WORKSPACE_MEMBER_IDS = {
}; };
export const seedWorkspaceMemberWithDemoData = async ( export const seedWorkspaceMemberWithDemoData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.workspaceMember`, [ .into(`${schemaName}.workspaceMember`, [
'id', 'id',

View File

@ -1,7 +1,8 @@
import { DataSource, EntityManager } from 'typeorm'; import { DataSource } from 'typeorm';
import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites'; import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { seedCompanyWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-company-with-demo-data'; import { seedCompanyWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-company-with-demo-data';
import { seedOpportunityWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-opportunity-with-demo-data'; import { seedOpportunityWithDemoData } from 'src/engine/workspace-manager/demo-objects-prefill-data/seed-opportunity-with-demo-data';
@ -28,7 +29,7 @@ export const seedWorkspaceWithDemoData = async (
}, {}); }, {});
await workspaceDataSource.transaction( await workspaceDataSource.transaction(
async (entityManager: EntityManager) => { async (entityManager: WorkspaceEntityManager) => {
await seedCompanyWithDemoData(entityManager, schemaName); await seedCompanyWithDemoData(entityManager, schemaName);
await seedPersonWithDemoData(entityManager, schemaName); await seedPersonWithDemoData(entityManager, schemaName);
await seedOpportunityWithDemoData(entityManager, schemaName); await seedOpportunityWithDemoData(entityManager, schemaName);

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f'; export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f';
export const QONTO_ID = 'f45ee421-8a3e-4aa5-a1cf-7207cc6754e1'; export const QONTO_ID = 'f45ee421-8a3e-4aa5-a1cf-7207cc6754e1';
@ -9,11 +8,13 @@ export const FIGMA_ID = '9d5bcf43-7d38-4e88-82cb-d6d4ce638bf0';
export const NOTION_ID = '06290608-8bf0-4806-99ae-a715a6a93fad'; export const NOTION_ID = '06290608-8bf0-4806-99ae-a715a6a93fad';
export const companyPrefillData = async ( export const companyPrefillData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.company`, [ .into(`${schemaName}.company`, [
'id', 'id',

View File

@ -1,10 +1,10 @@
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface'; import { ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
export const createWorkspaceViews = async ( export const createWorkspaceViews = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
viewDefinitions: ViewDefinition[], viewDefinitions: ViewDefinition[],
) => { ) => {
@ -14,7 +14,9 @@ export const createWorkspaceViews = async (
})); }));
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.view`, [ .into(`${schemaName}.view`, [
'id', 'id',
@ -64,7 +66,9 @@ export const createWorkspaceViews = async (
for (const viewDefinition of viewDefinitionsWithId) { for (const viewDefinition of viewDefinitionsWithId) {
if (viewDefinition.fields && viewDefinition.fields.length > 0) { if (viewDefinition.fields && viewDefinition.fields.length > 0) {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.viewField`, [ .into(`${schemaName}.viewField`, [
'fieldMetadataId', 'fieldMetadataId',
@ -89,7 +93,9 @@ export const createWorkspaceViews = async (
if (viewDefinition.filters && viewDefinition.filters.length > 0) { if (viewDefinition.filters && viewDefinition.filters.length > 0) {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.viewFilter`, [ .into(`${schemaName}.viewFilter`, [
'fieldMetadataId', 'fieldMetadataId',
@ -116,7 +122,9 @@ export const createWorkspaceViews = async (
viewDefinition.groups.length > 0 viewDefinition.groups.length > 0
) { ) {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.viewGroup`, [ .into(`${schemaName}.viewGroup`, [
'fieldMetadataId', 'fieldMetadataId',

View File

@ -1,6 +1,5 @@
import { EntityManager } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { import {
AIRBNB_ID, AIRBNB_ID,
FIGMA_ID, FIGMA_ID,
@ -11,11 +10,13 @@ import {
// FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts // FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts
export const personPrefillData = async ( export const personPrefillData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
) => { ) => {
await entityManager await entityManager
.createQueryBuilder() .createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert() .insert()
.into(`${schemaName}.person`, [ .into(`${schemaName}.person`, [
'nameFirstName', 'nameFirstName',

View File

@ -1,7 +1,6 @@
import { EntityManager } from 'typeorm';
import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map'; import { ObjectMetadataStandardIdToIdMap } from 'src/engine/metadata-modules/object-metadata/interfaces/object-metadata-standard-id-to-id-map';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views'; import { createWorkspaceViews } from 'src/engine/workspace-manager/standard-objects-prefill-data/create-workspace-views';
import { seedCompaniesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view'; import { seedCompaniesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view';
import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view'; import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view';
@ -16,7 +15,7 @@ import { workflowVersionsAllView } from 'src/engine/workspace-manager/standard-o
import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view'; import { workflowsAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/workflows-all.view';
export const seedViewWithDemoData = async ( export const seedViewWithDemoData = async (
entityManager: EntityManager, entityManager: WorkspaceEntityManager,
schemaName: string, schemaName: string,
objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap, objectMetadataStandardIdToIdMap: ObjectMetadataStandardIdToIdMap,
) => { ) => {

View File

@ -1,7 +1,8 @@
import { DataSource, EntityManager } from 'typeorm'; import { DataSource } from 'typeorm';
import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites'; import { seedWorkspaceFavorites } from 'src/database/typeorm-seeds/workspace/favorites';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite'; import { shouldSeedWorkspaceFavorite } from 'src/engine/utils/should-seed-workspace-favorite';
import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company'; import { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company';
import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person'; import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person';
@ -33,28 +34,30 @@ export const standardObjectsPrefillData = async (
return acc; return acc;
}, {}); }, {});
workspaceDataSource.transaction(async (entityManager: EntityManager) => { workspaceDataSource.transaction(
await companyPrefillData(entityManager, schemaName); async (entityManager: WorkspaceEntityManager) => {
await personPrefillData(entityManager, schemaName); await companyPrefillData(entityManager, schemaName);
const viewDefinitionsWithId = await seedViewWithDemoData( await personPrefillData(entityManager, schemaName);
entityManager, const viewDefinitionsWithId = await seedViewWithDemoData(
schemaName, entityManager,
objectMetadataMap, schemaName,
); objectMetadataMap,
);
await seedWorkspaceFavorites( await seedWorkspaceFavorites(
viewDefinitionsWithId viewDefinitionsWithId
.filter( .filter(
(view) => (view) =>
view.key === 'INDEX' && view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite( shouldSeedWorkspaceFavorite(
view.objectMetadataId, view.objectMetadataId,
objectMetadataMap, objectMetadataMap,
), ),
) )
.map((view) => view.id), .map((view) => view.id),
entityManager, entityManager,
schemaName, schemaName,
); );
}); },
);
}; };

View File

@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity'; import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
@ -14,7 +12,6 @@ export class BlocklistRepository {
public async getById( public async getById(
id: string, id: string,
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
): Promise<BlocklistWorkspaceEntity | null> { ): Promise<BlocklistWorkspaceEntity | null> {
const dataSourceSchema = const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -24,7 +21,6 @@ export class BlocklistRepository {
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "id" = $1`, `SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "id" = $1`,
[id], [id],
workspaceId, workspaceId,
transactionManager,
); );
if (!blocklistItems || blocklistItems.length === 0) { if (!blocklistItems || blocklistItems.length === 0) {
@ -37,7 +33,6 @@ export class BlocklistRepository {
public async getByWorkspaceMemberId( public async getByWorkspaceMemberId(
workspaceMemberId: string, workspaceMemberId: string,
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
): Promise<BlocklistWorkspaceEntity[]> { ): Promise<BlocklistWorkspaceEntity[]> {
const dataSourceSchema = const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -46,7 +41,6 @@ export class BlocklistRepository {
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "workspaceMemberId" = $1`, `SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "workspaceMemberId" = $1`,
[workspaceMemberId], [workspaceMemberId],
workspaceId, workspaceId,
transactionManager,
); );
} }
} }

View File

@ -6,6 +6,7 @@ import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decora
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants'; import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { injectIdsInCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/inject-ids-in-calendar-events.util'; import { injectIdsInCalendarEvents } from 'src/modules/calendar/calendar-event-import-manager/utils/inject-ids-in-calendar-events.util';
import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service'; import { CalendarEventParticipantService } from 'src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service';
@ -113,71 +114,73 @@ export class CalendarSaveEventsService {
const workspaceDataSource = await this.twentyORMManager.getDatasource(); const workspaceDataSource = await this.twentyORMManager.getDatasource();
await workspaceDataSource?.transaction(async (transactionManager) => { await workspaceDataSource?.transaction(
await calendarEventRepository.save( async (transactionManager: WorkspaceEntityManager) => {
eventsToSave.map( await calendarEventRepository.save(
(calendarEvent) => eventsToSave.map(
({ (calendarEvent) =>
id: calendarEvent.id, ({
iCalUID: calendarEvent.iCalUID, id: calendarEvent.id,
title: calendarEvent.title, iCalUID: calendarEvent.iCalUID,
description: calendarEvent.description, title: calendarEvent.title,
startsAt: calendarEvent.startsAt, description: calendarEvent.description,
endsAt: calendarEvent.endsAt, startsAt: calendarEvent.startsAt,
location: calendarEvent.location, endsAt: calendarEvent.endsAt,
isFullDay: calendarEvent.isFullDay, location: calendarEvent.location,
isCanceled: calendarEvent.isCanceled, isFullDay: calendarEvent.isFullDay,
conferenceSolution: calendarEvent.conferenceSolution, isCanceled: calendarEvent.isCanceled,
conferenceLink: { conferenceSolution: calendarEvent.conferenceSolution,
primaryLinkLabel: calendarEvent.conferenceLinkLabel, conferenceLink: {
primaryLinkUrl: calendarEvent.conferenceLinkUrl, primaryLinkLabel: calendarEvent.conferenceLinkLabel,
}, primaryLinkUrl: calendarEvent.conferenceLinkUrl,
externalCreatedAt: calendarEvent.externalCreatedAt, },
externalUpdatedAt: calendarEvent.externalUpdatedAt, externalCreatedAt: calendarEvent.externalCreatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>, externalUpdatedAt: calendarEvent.externalUpdatedAt,
), }) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
{}, ),
transactionManager, {},
); transactionManager,
);
await calendarEventRepository.save( await calendarEventRepository.save(
eventsToUpdate.map( eventsToUpdate.map(
(calendarEvent) => (calendarEvent) =>
({ ({
id: calendarEvent.id, id: calendarEvent.id,
iCalUID: calendarEvent.iCalUID, iCalUID: calendarEvent.iCalUID,
title: calendarEvent.title, title: calendarEvent.title,
description: calendarEvent.description, description: calendarEvent.description,
startsAt: calendarEvent.startsAt, startsAt: calendarEvent.startsAt,
endsAt: calendarEvent.endsAt, endsAt: calendarEvent.endsAt,
location: calendarEvent.location, location: calendarEvent.location,
isFullDay: calendarEvent.isFullDay, isFullDay: calendarEvent.isFullDay,
isCanceled: calendarEvent.isCanceled, isCanceled: calendarEvent.isCanceled,
conferenceSolution: calendarEvent.conferenceSolution, conferenceSolution: calendarEvent.conferenceSolution,
conferenceLink: { conferenceLink: {
primaryLinkLabel: calendarEvent.conferenceLinkLabel, primaryLinkLabel: calendarEvent.conferenceLinkLabel,
primaryLinkUrl: calendarEvent.conferenceLinkUrl, primaryLinkUrl: calendarEvent.conferenceLinkUrl,
}, },
externalCreatedAt: calendarEvent.externalCreatedAt, externalCreatedAt: calendarEvent.externalCreatedAt,
externalUpdatedAt: calendarEvent.externalUpdatedAt, externalUpdatedAt: calendarEvent.externalUpdatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>, }) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
), ),
{}, {},
transactionManager, transactionManager,
); );
await calendarChannelEventAssociationRepository.save( await calendarChannelEventAssociationRepository.save(
calendarChannelEventAssociationsToSave, calendarChannelEventAssociationsToSave,
{}, {},
transactionManager, transactionManager,
); );
await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants( await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants(
participantsToSave, participantsToSave,
participantsToUpdate, participantsToUpdate,
transactionManager, transactionManager,
); );
}); },
);
if (calendarChannel.isContactAutoCreationEnabled) { if (calendarChannel.isContactAutoCreationEnabled) {
await this.messageQueueService.add<CreateCompanyAndContactJobData>( await this.messageQueueService.add<CreateCompanyAndContactJobData>(

View File

@ -4,6 +4,7 @@ import { isDefined } from 'class-validator';
import differenceWith from 'lodash.differencewith'; import differenceWith from 'lodash.differencewith';
import { Any } from 'typeorm'; import { Any } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity'; import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
import { CalendarEventParticipantWithCalendarEventId } from 'src/modules/calendar/common/types/calendar-event'; import { CalendarEventParticipantWithCalendarEventId } from 'src/modules/calendar/common/types/calendar-event';
@ -19,7 +20,7 @@ export class CalendarEventParticipantService {
public async upsertAndDeleteCalendarEventParticipants( public async upsertAndDeleteCalendarEventParticipants(
participantsToSave: CalendarEventParticipantWithCalendarEventId[], participantsToSave: CalendarEventParticipantWithCalendarEventId[],
participantsToUpdate: CalendarEventParticipantWithCalendarEventId[], participantsToUpdate: CalendarEventParticipantWithCalendarEventId[],
transactionManager?: any, transactionManager?: WorkspaceEntityManager,
): Promise<void> { ): Promise<void> {
const calendarEventParticipantRepository = const calendarEventParticipantRepository =
await this.twentyORMManager.getRepository<CalendarEventParticipantWorkspaceEntity>( await this.twentyORMManager.getRepository<CalendarEventParticipantWorkspaceEntity>(

View File

@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import chunk from 'lodash.chunk'; import chunk from 'lodash.chunk';
import compact from 'lodash.compact'; import compact from 'lodash.compact';
import { Any, EntityManager, Repository } from 'typeorm'; import { Any, Repository } from 'typeorm';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
@ -45,7 +45,6 @@ export class CreateCompanyAndContactService {
contactsToCreate: Contact[], contactsToCreate: Contact[],
workspaceId: string, workspaceId: string,
source: FieldActorSource, source: FieldActorSource,
transactionManager?: EntityManager,
): Promise<DeepPartial<PersonWorkspaceEntity>[]> { ): Promise<DeepPartial<PersonWorkspaceEntity>[]> {
if (!contactsToCreate || contactsToCreate.length === 0) { if (!contactsToCreate || contactsToCreate.length === 0) {
return []; return [];
@ -61,10 +60,7 @@ export class CreateCompanyAndContactService {
); );
const workspaceMembers = const workspaceMembers =
await this.workspaceMemberRepository.getAllByWorkspaceId( await this.workspaceMemberRepository.getAllByWorkspaceId(workspaceId);
workspaceId,
transactionManager,
);
const contactsToCreateFromOtherCompanies = const contactsToCreateFromOtherCompanies =
filterOutSelfAndContactsFromCompanyOrWorkspace( filterOutSelfAndContactsFromCompanyOrWorkspace(
@ -137,7 +133,6 @@ export class CreateCompanyAndContactService {
const companiesObject = await this.createCompaniesService.createCompanies( const companiesObject = await this.createCompaniesService.createCompanies(
workDomainNamesToCreateFormatted, workDomainNamesToCreateFormatted,
workspaceId, workspaceId,
transactionManager,
); );
const formattedContactsToCreate = const formattedContactsToCreate =
@ -158,7 +153,6 @@ export class CreateCompanyAndContactService {
return this.createContactService.createPeople( return this.createContactService.createPeople(
formattedContactsToCreate, formattedContactsToCreate,
workspaceId, workspaceId,
transactionManager,
); );
} }

View File

@ -4,7 +4,7 @@ import axios, { AxiosInstance } from 'axios';
import uniqBy from 'lodash.uniqby'; import uniqBy from 'lodash.uniqby';
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants'; import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { DeepPartial, EntityManager, ILike } from 'typeorm'; import { DeepPartial, ILike } from 'typeorm';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@ -37,7 +37,6 @@ export class CreateCompanyService {
async createCompanies( async createCompanies(
companies: CompanyToCreate[], companies: CompanyToCreate[],
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
): Promise<{ ): Promise<{
[domainName: string]: string; [domainName: string]: string;
}> { }> {
@ -63,12 +62,9 @@ export class CreateCompanyService {
})); }));
// Find existing companies // Find existing companies
const existingCompanies = await companyRepository.find( const existingCompanies = await companyRepository.find({
{ where: conditions,
where: conditions, });
},
transactionManager,
);
const existingCompanyIdsMap = this.createCompanyMap(existingCompanies); const existingCompanyIdsMap = this.createCompanyMap(existingCompanies);
// Filter out companies that already exist // Filter out companies that already exist
@ -87,10 +83,8 @@ export class CreateCompanyService {
} }
// Retrieve the last company position // Retrieve the last company position
let lastCompanyPosition = await this.getLastCompanyPosition( let lastCompanyPosition =
companyRepository, await this.getLastCompanyPosition(companyRepository);
transactionManager,
);
const newCompaniesData = await Promise.all( const newCompaniesData = await Promise.all(
newCompaniesToCreate.map((company) => newCompaniesToCreate.map((company) =>
this.prepareCompanyData(company, ++lastCompanyPosition), this.prepareCompanyData(company, ++lastCompanyPosition),
@ -156,12 +150,10 @@ export class CreateCompanyService {
private async getLastCompanyPosition( private async getLastCompanyPosition(
companyRepository: WorkspaceRepository<CompanyWorkspaceEntity>, companyRepository: WorkspaceRepository<CompanyWorkspaceEntity>,
transactionManager?: EntityManager,
): Promise<number> { ): Promise<number> {
const lastCompanyPosition = await companyRepository.maximum( const lastCompanyPosition = await companyRepository.maximum(
'position', 'position',
undefined, undefined,
transactionManager,
); );
return lastCompanyPosition ?? 0; return lastCompanyPosition ?? 0;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { DeepPartial, EntityManager } from 'typeorm'; import { DeepPartial } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
@ -74,7 +74,6 @@ export class CreateContactService {
public async createPeople( public async createPeople(
contactsToCreate: ContactToCreate[], contactsToCreate: ContactToCreate[],
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
): Promise<DeepPartial<PersonWorkspaceEntity>[]> { ): Promise<DeepPartial<PersonWorkspaceEntity>[]> {
if (contactsToCreate.length === 0) return []; if (contactsToCreate.length === 0) return [];
@ -87,31 +86,23 @@ export class CreateContactService {
}, },
); );
const lastPersonPosition = await this.getLastPersonPosition( const lastPersonPosition =
personRepository, await this.getLastPersonPosition(personRepository);
transactionManager,
);
const formattedContacts = this.formatContacts( const formattedContacts = this.formatContacts(
contactsToCreate, contactsToCreate,
lastPersonPosition, lastPersonPosition,
); );
return personRepository.save( return personRepository.save(formattedContacts, undefined);
formattedContacts,
undefined,
transactionManager,
);
} }
private async getLastPersonPosition( private async getLastPersonPosition(
personRepository: WorkspaceRepository<PersonWorkspaceEntity>, personRepository: WorkspaceRepository<PersonWorkspaceEntity>,
transactionManager?: EntityManager,
): Promise<number> { ): Promise<number> {
const lastPersonPosition = await personRepository.maximum( const lastPersonPosition = await personRepository.maximum(
'position', 'position',
undefined, undefined,
transactionManager,
); );
return lastPersonPosition ?? 0; return lastPersonPosition ?? 0;

View File

@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { Any, EntityManager, Equal } from 'typeorm'; import { Any, Equal } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
@ -39,7 +40,7 @@ export class MatchParticipantService<
public async matchParticipants( public async matchParticipants(
participants: ParticipantWorkspaceEntity[], participants: ParticipantWorkspaceEntity[],
objectMetadataName: 'messageParticipant' | 'calendarEventParticipant', objectMetadataName: 'messageParticipant' | 'calendarEventParticipant',
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) { ) {
const participantRepository = const participantRepository =
await this.getParticipantRepository(objectMetadataName); await this.getParticipantRepository(objectMetadataName);

View File

@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager, IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity'; import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
@ -24,75 +25,77 @@ export class MessagingMessageCleanerService {
const workspaceDataSource = await this.twentyORMManager.getDatasource(); const workspaceDataSource = await this.twentyORMManager.getDatasource();
await workspaceDataSource.transaction(async (transactionManager) => { await workspaceDataSource.transaction(
await deleteUsingPagination( async (transactionManager: WorkspaceEntityManager) => {
workspaceId, await deleteUsingPagination(
500, workspaceId,
async ( 500,
limit: number, async (
offset: number, limit: number,
workspaceId: string, offset: number,
transactionManager: EntityManager, _workspaceId: string,
) => { transactionManager: WorkspaceEntityManager,
const nonAssociatedMessages = await messageRepository.find( ) => {
{ const nonAssociatedMessages = await messageRepository.find(
where: { {
messageChannelMessageAssociations: { where: {
id: IsNull(), messageChannelMessageAssociations: {
id: IsNull(),
},
}, },
take: limit,
skip: offset,
relations: ['messageChannelMessageAssociations'],
}, },
take: limit, transactionManager,
skip: offset, );
relations: ['messageChannelMessageAssociations'],
},
transactionManager,
);
return nonAssociatedMessages.map(({ id }) => id); return nonAssociatedMessages.map(({ id }) => id);
}, },
async ( async (
ids: string[], ids: string[],
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => { ) => {
await messageRepository.delete(ids, transactionManager); await messageRepository.delete(ids, transactionManager);
}, },
transactionManager, transactionManager,
); );
await deleteUsingPagination( await deleteUsingPagination(
workspaceId, workspaceId,
500, 500,
async ( async (
limit: number, limit: number,
offset: number, offset: number,
workspaceId: string, _workspaceId: string,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => { ) => {
const orphanThreads = await messageThreadRepository.find( const orphanThreads = await messageThreadRepository.find(
{ {
where: { where: {
messages: { messages: {
id: IsNull(), id: IsNull(),
},
}, },
take: limit,
skip: offset,
}, },
take: limit, transactionManager,
skip: offset, );
},
transactionManager,
);
return orphanThreads.map(({ id }) => id); return orphanThreads.map(({ id }) => id);
}, },
async ( async (
ids: string[], ids: string[],
workspaceId: string, _workspaceId: string,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => { ) => {
await messageThreadRepository.delete(ids, transactionManager); await messageThreadRepository.delete(ids, transactionManager);
}, },
transactionManager, transactionManager,
); );
}); },
);
} }
} }

View File

@ -1,4 +1,4 @@
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
export const deleteUsingPagination = async ( export const deleteUsingPagination = async (
workspaceId: string, workspaceId: string,
@ -7,14 +7,14 @@ export const deleteUsingPagination = async (
limit: number, limit: number,
offset: number, offset: number,
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => Promise<string[]>, ) => Promise<string[]>,
deleter: ( deleter: (
ids: string[], ids: string[],
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => Promise<void>, ) => Promise<void>,
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
) => { ) => {
let hasMoreData = true; let hasMoreData = true;

View File

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid'; import { v4 } from 'uuid';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity'; import { MessageChannelMessageAssociationWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel-message-association.workspace-entity';
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity'; import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
@ -16,7 +16,7 @@ export class MessagingMessageService {
public async saveMessagesWithinTransaction( public async saveMessagesWithinTransaction(
messages: MessageWithParticipants[], messages: MessageWithParticipants[],
messageChannelId: string, messageChannelId: string,
transactionManager: EntityManager, transactionManager: WorkspaceEntityManager,
): Promise<{ ): Promise<{
createdMessages: Partial<MessageWorkspaceEntity>[]; createdMessages: Partial<MessageWorkspaceEntity>[];
messageExternalIdsAndIdsMap: Map<string, string>; messageExternalIdsAndIdsMap: Map<string, string>;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator'; import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
@ -9,6 +9,7 @@ import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queu
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity'; import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@ -55,7 +56,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
const createdMessagesWithParticipants = const createdMessagesWithParticipants =
await workspaceDataSource?.transaction( await workspaceDataSource?.transaction(
async (transactionManager: EntityManager) => { async (transactionManager: WorkspaceEntityManager) => {
const { messageExternalIdsAndIdsMap, createdMessages } = const { messageExternalIdsAndIdsMap, createdMessages } =
await this.messageService.saveMessagesWithinTransaction( await this.messageService.saveMessagesWithinTransaction(
messagesToSave, messagesToSave,

View File

@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { MatchParticipantService } from 'src/modules/match-participant/match-participant.service'; import { MatchParticipantService } from 'src/modules/match-participant/match-participant.service';
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity'; import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
@ -16,7 +15,7 @@ export class MessagingMessageParticipantService {
public async saveMessageParticipants( public async saveMessageParticipants(
participants: ParticipantWithMessageId[], participants: ParticipantWithMessageId[],
transactionManager?: EntityManager, transactionManager?: WorkspaceEntityManager,
): Promise<void> { ): Promise<void> {
const messageParticipantRepository = const messageParticipantRepository =
await this.twentyORMManager.getRepository<MessageParticipantWorkspaceEntity>( await this.twentyORMManager.getRepository<MessageParticipantWorkspaceEntity>(

View File

@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface'; import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { objectRecordDiffMerge } from 'src/engine/core-modules/event-emitter/utils/object-record-diff-merge'; import { objectRecordDiffMerge } from 'src/engine/core-modules/event-emitter/utils/object-record-diff-merge';
@ -159,7 +157,6 @@ export class TimelineActivityRepository {
linkedObjectMetadataId: string | undefined; linkedObjectMetadataId: string | undefined;
}[], }[],
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
) { ) {
if (activities.length === 0) { if (activities.length === 0) {
return; return;
@ -191,7 +188,6 @@ export class TimelineActivityRepository {
]) ])
.flat(), .flat(),
workspaceId, workspaceId,
transactionManager,
); );
} }
} }

View File

@ -1,14 +1,13 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm'; import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import { import {
AutomatedTriggerType,
AutomatedTriggerSettings, AutomatedTriggerSettings,
AutomatedTriggerType,
WorkflowAutomatedTriggerWorkspaceEntity, WorkflowAutomatedTriggerWorkspaceEntity,
} from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity'; } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
import { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
@Injectable() @Injectable()
export class AutomatedTriggerWorkspaceService { export class AutomatedTriggerWorkspaceService {
@ -21,7 +20,7 @@ export class AutomatedTriggerWorkspaceService {
settings, settings,
}: { }: {
workflowId: string; workflowId: string;
manager: EntityManager; manager: WorkspaceEntityManager;
type: AutomatedTriggerType; type: AutomatedTriggerType;
settings: AutomatedTriggerSettings; settings: AutomatedTriggerSettings;
}) { }) {
@ -68,7 +67,7 @@ export class AutomatedTriggerWorkspaceService {
manager, manager,
}: { }: {
workflowId: string; workflowId: string;
manager: EntityManager; manager: WorkspaceEntityManager;
}) { }) {
// Todo: remove workflowEventListenerRepository updates when data are migrated to workflowAutomatedTrigger // Todo: remove workflowEventListenerRepository updates when data are migrated to workflowAutomatedTrigger
const workflowEventListenerRepository = const workflowEventListenerRepository =

View File

@ -1,15 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { EntityManager, Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action'; import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type'; import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory'; import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository'; import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter'; import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
import { import {
WorkflowVersionStatus, WorkflowVersionStatus,
WorkflowVersionWorkspaceEntity, WorkflowVersionWorkspaceEntity,
@ -20,6 +22,7 @@ import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/work
import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service'; import { WorkflowRunnerWorkspaceService } from 'src/modules/workflow/workflow-runner/workspace-services/workflow-runner.workspace-service';
import { WORKFLOW_VERSION_STATUS_UPDATED } from 'src/modules/workflow/workflow-status/constants/workflow-version-status-updated.constants'; import { WORKFLOW_VERSION_STATUS_UPDATED } from 'src/modules/workflow/workflow-status/constants/workflow-version-status-updated.constants';
import { WorkflowVersionStatusUpdate } from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job'; import { WorkflowVersionStatusUpdate } from 'src/modules/workflow/workflow-status/jobs/workflow-statuses-update.job';
import { AutomatedTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service';
import { import {
WorkflowTriggerException, WorkflowTriggerException,
WorkflowTriggerExceptionCode, WorkflowTriggerExceptionCode,
@ -28,8 +31,6 @@ import { WorkflowTriggerType } from 'src/modules/workflow/workflow-trigger/types
import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util'; import { assertVersionCanBeActivated } from 'src/modules/workflow/workflow-trigger/utils/assert-version-can-be-activated.util';
import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule'; import { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
import { assertNever } from 'src/utils/assert'; import { assertNever } from 'src/utils/assert';
import { AutomatedTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service';
import { AutomatedTriggerType } from 'src/modules/workflow/common/standard-objects/workflow-automated-trigger.workspace-entity';
@Injectable() @Injectable()
export class WorkflowTriggerWorkspaceService { export class WorkflowTriggerWorkspaceService {
@ -174,7 +175,7 @@ export class WorkflowTriggerWorkspaceService {
workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersion: WorkflowVersionWorkspaceEntity,
workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>, workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>, workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
if ( if (
workflow.lastPublishedVersionId && workflow.lastPublishedVersionId &&
@ -207,7 +208,7 @@ export class WorkflowTriggerWorkspaceService {
private async performDeactivationSteps( private async performDeactivationSteps(
workflowVersionId: string, workflowVersionId: string,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>, workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
const workflowVersionNullable = await workflowVersionRepository.findOne({ const workflowVersionNullable = await workflowVersionRepository.findOne({
where: { id: workflowVersionId }, where: { id: workflowVersionId },
@ -234,7 +235,7 @@ export class WorkflowTriggerWorkspaceService {
private async setActiveVersionStatus( private async setActiveVersionStatus(
workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersion: WorkflowVersionWorkspaceEntity,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>, workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
const activeWorkflowVersions = await workflowVersionRepository.find( const activeWorkflowVersions = await workflowVersionRepository.find(
{ {
@ -269,7 +270,7 @@ export class WorkflowTriggerWorkspaceService {
private async setDeactivatedVersionStatus( private async setDeactivatedVersionStatus(
workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersion: WorkflowVersionWorkspaceEntity,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>, workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) { if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) {
throw new WorkflowTriggerException( throw new WorkflowTriggerException(
@ -296,7 +297,7 @@ export class WorkflowTriggerWorkspaceService {
newPublishedVersionId: string, newPublishedVersionId: string,
workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>, workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>, workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
if (workflow.lastPublishedVersionId === newPublishedVersionId) { if (workflow.lastPublishedVersionId === newPublishedVersionId) {
return; return;
@ -319,7 +320,7 @@ export class WorkflowTriggerWorkspaceService {
private async enableTrigger( private async enableTrigger(
workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersion: WorkflowVersionWorkspaceEntity,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
assertWorkflowVersionTriggerIsDefined(workflowVersion); assertWorkflowVersionTriggerIsDefined(workflowVersion);
@ -359,7 +360,7 @@ export class WorkflowTriggerWorkspaceService {
private async disableTrigger( private async disableTrigger(
workflowVersion: WorkflowVersionWorkspaceEntity, workflowVersion: WorkflowVersionWorkspaceEntity,
manager: EntityManager, manager: WorkspaceEntityManager,
) { ) {
assertWorkflowVersionTriggerIsDefined(workflowVersion); assertWorkflowVersionTriggerIsDefined(workflowVersion);

View File

@ -1,7 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common'; import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service'; import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@ -50,7 +48,6 @@ export class WorkspaceMemberRepository {
public async getAllByWorkspaceId( public async getAllByWorkspaceId(
workspaceId: string, workspaceId: string,
transactionManager?: EntityManager,
): Promise<WorkspaceMemberWorkspaceEntity[]> { ): Promise<WorkspaceMemberWorkspaceEntity[]> {
const dataSourceSchema = const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId); this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -60,7 +57,6 @@ export class WorkspaceMemberRepository {
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`, `SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
[], [],
workspaceId, workspaceId,
transactionManager,
); );
return workspaceMembers; return workspaceMembers;

View File

@ -2,9 +2,12 @@ import { randomUUID } from 'node:crypto';
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util'; import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
import { makeGraphqlAPIRequestWithApiKey } from 'test/integration/graphql/utils/make-graphql-api-request-with-api-key.util';
import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util'; import { makeGraphqlAPIRequestWithGuestRole } from 'test/integration/graphql/utils/make-graphql-api-request-with-guest-role.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { updateFeatureFlagFactory } from 'test/integration/graphql/utils/update-feature-flag-factory.util';
import { SEED_APPLE_WORKSPACE_ID } from 'src/database/typeorm-seeds/core/workspaces';
import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception'; import { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
@ -48,79 +51,79 @@ describe('createOneObjectRecordsPermissions', () => {
}); });
}); });
// describe('permissions V2 enabled', () => { describe('permissions V2 enabled', () => {
// beforeAll(async () => { beforeAll(async () => {
// const enablePermissionsQuery = updateFeatureFlagFactory( const enablePermissionsQuery = updateFeatureFlagFactory(
// SEED_APPLE_WORKSPACE_ID, SEED_APPLE_WORKSPACE_ID,
// 'IsPermissionsV2Enabled', 'IsPermissionsV2Enabled',
// true, true,
// ); );
// await makeGraphqlAPIRequest(enablePermissionsQuery); await makeGraphqlAPIRequest(enablePermissionsQuery);
// }); });
// afterAll(async () => { afterAll(async () => {
// const disablePermissionsQuery = updateFeatureFlagFactory( const disablePermissionsQuery = updateFeatureFlagFactory(
// SEED_APPLE_WORKSPACE_ID, SEED_APPLE_WORKSPACE_ID,
// 'IsPermissionsV2Enabled', 'IsPermissionsV2Enabled',
// false, false,
// ); );
// await makeGraphqlAPIRequest(disablePermissionsQuery); await makeGraphqlAPIRequest(disablePermissionsQuery);
// }); });
// it('should throw a permission error when user does not have permission (guest role)', async () => { it('should throw a permission error when user does not have permission (guest role)', async () => {
// const graphqlOperation = createOneOperationFactory({ const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,
// data: { data: {
// id: randomUUID(), id: randomUUID(),
// }, },
// }); });
// const response = const response =
// await makeGraphqlAPIRequestWithGuestRole(graphqlOperation); await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
// expect(response.body.data).toStrictEqual({ createPerson: null }); expect(response.body.data).toStrictEqual({ createPerson: null });
// expect(response.body.errors).toBeDefined(); expect(response.body.errors).toBeDefined();
// expect(response.body.errors[0].message).toBe( expect(response.body.errors[0].message).toBe(
// PermissionsExceptionMessage.PERMISSION_DENIED, PermissionsExceptionMessage.PERMISSION_DENIED,
// ); );
// expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN); expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
// }); });
// it('should create an object record when user has permission (admin role)', async () => { it('should create an object record when user has permission (admin role)', async () => {
// const personId = randomUUID(); const personId = randomUUID();
// const graphqlOperation = createOneOperationFactory({ const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,
// data: { data: {
// id: personId, id: personId,
// }, },
// }); });
// const response = await makeGraphqlAPIRequest(graphqlOperation); const response = await makeGraphqlAPIRequest(graphqlOperation);
// expect(response.body.data).toBeDefined(); expect(response.body.data).toBeDefined();
// expect(response.body.data.createPerson).toBeDefined(); expect(response.body.data.createPerson).toBeDefined();
// expect(response.body.data.createPerson.id).toBe(personId); expect(response.body.data.createPerson.id).toBe(personId);
// }); });
// it('should create an object record when executed by api key', async () => { it('should create an object record when executed by api key', async () => {
// const personId = randomUUID(); const personId = randomUUID();
// const graphqlOperation = createOneOperationFactory({ const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person', objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS, gqlFields: PERSON_GQL_FIELDS,
// data: { data: {
// id: personId, id: personId,
// }, },
// }); });
// const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation); const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
// expect(response.body.data).toBeDefined(); expect(response.body.data).toBeDefined();
// expect(response.body.data.createPerson).toBeDefined(); expect(response.body.data.createPerson).toBeDefined();
// expect(response.body.data.createPerson.id).toBe(personId); expect(response.body.data.createPerson.id).toBe(personId);
// }); });
// }); });
}); });