[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 { Command, CommandRunner } from 'nest-commander';
import { DataSource, EntityManager } from 'typeorm';
import { DataSource } from 'typeorm';
import { seedCoreSchema } from 'src/database/typeorm-seeds/core';
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 { SURVEY_RESULTS_METADATA_SEEDS } from 'src/engine/seeder/metadata-seeds/survey-results-metadata-seeds';
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 { 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';
@ -169,7 +170,7 @@ export class DataSeedWorkspaceCommand extends CommandRunner {
dataSourceMetadata: DataSourceEntity,
) {
await workspaceDataSource.transaction(
async (entityManager: EntityManager) => {
async (entityManager: WorkspaceEntityManager) => {
const { objectMetadataStandardIdToIdMap } =
await this.objectMetadataService.getObjectMetadataStandardIdToIdMap(
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 API_KEY_ID = '20202020-f401-4d8a-a731-64d007c27bad';
export const seedApiKey = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'name', 'expiresAt'])
.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';
export const seedCalendarChannelEventAssociations = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { CalendarChannelVisibility } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
const tableName = 'calendarChannel';
export const seedCalendarChannels = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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_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';
const tableName = 'calendarEventParticipant';
export const seedCalendarEventParticipants = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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';
export const seedCalendarEvents = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'company';
@ -21,11 +20,13 @@ export const DEV_SEED_COMPANY_IDS = {
};
export const seedCompanies = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'connectedAccount';
@ -11,11 +10,13 @@ export const DEV_SEED_CONNECTED_ACCOUNT_IDS = {
};
export const seedConnectedAccount = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'id',

View File

@ -1,15 +1,18 @@
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'favorite';
export const seedWorkspaceFavorites = async (
viewIds: string[],
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, ['id', 'viewId', 'position'])
.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_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';
const tableName = 'messageChannelMessageAssociation';
@ -13,11 +12,13 @@ export const DEV_SEED_MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_IDS = {
};
export const seedMessageChannelMessageAssociation = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import {
MessageChannelSyncStage,
MessageChannelVisibility,
@ -15,11 +14,13 @@ export const DEV_SEED_MESSAGE_CHANNEL_IDS = {
};
export const seedMessageChannel = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople';
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';
@ -16,11 +15,13 @@ export const DEV_SEED_MESSAGE_PARTICIPANT_IDS = {
};
export const seedMessageParticipant = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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';
@ -26,11 +26,13 @@ export const DEV_SEED_USER_IDS = {
};
export const seedMessageThreadSubscribers = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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';
@ -11,11 +11,13 @@ export const DEV_SEED_MESSAGE_THREAD_IDS = {
};
export const seedMessageThread = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
const tableName = 'message';
@ -11,11 +10,13 @@ export const DEV_SEED_MESSAGE_IDS = {
};
export const seedMessage = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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_PERSON_IDS } from 'src/database/typeorm-seeds/workspace/seedPeople';
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';
@ -14,11 +13,13 @@ export const DEV_SEED_OPPORTUNITY_IDS = {
};
export const seedOpportunity = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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_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';
@ -24,11 +23,13 @@ export const DEV_SEED_PERSON_IDS = {
};
export const seedPeople = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'id',

View File

@ -1,11 +1,10 @@
import { EntityManager } from 'typeorm';
import { DEV_SEED_USER_IDS } from 'src/database/typeorm-seeds/core/users';
import {
SEED_APPLE_WORKSPACE_ID,
SEED_ACME_WORKSPACE_ID,
SEED_APPLE_WORKSPACE_ID,
} from 'src/database/typeorm-seeds/core/workspaces';
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';
@ -26,7 +25,7 @@ type WorkspaceMembers = Pick<
};
export const seedWorkspaceMember = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
workspaceId: string,
) => {
@ -78,7 +77,9 @@ export const seedWorkspaceMember = async (
];
}
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'id',

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { EntityManager, Repository } from 'typeorm';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import {
@ -103,202 +104,208 @@ export class GoogleAPIsService {
const scopes = getGoogleApisOauthScopes();
await workspaceDataSource.transaction(async (manager: EntityManager) => {
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: [
await workspaceDataSource.transaction(
async (manager: WorkspaceEntityManager) => {
if (!existingAccountId) {
const newConnectedAccount = await connectedAccountRepository.save(
{
recordId: newConnectedAccount.id,
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,
id: newOrExistingConnectedAccountId,
handle,
visibility:
calendarVisibility ||
CalendarChannelVisibility.SHARE_EVERYTHING,
provider: ConnectedAccountProvider.GOOGLE,
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
},
{},
manager,
);
const calendarChannelMetadata =
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId },
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel',
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newCalendarChannel.id,
objectMetadata: calendarChannelMetadata,
recordId: newConnectedAccount.id,
objectMetadata: connectedAccountMetadata,
properties: {
after: newCalendarChannel,
after: newConnectedAccount,
},
},
],
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: [
const newMessageChannel = await messageChannelRepository.save(
{
recordId: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
before: connectedAccount,
after: {
...connectedAccount,
...updatedConnectedAccount.raw[0],
},
},
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
],
workspaceId,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
{},
manager,
);
const workspaceMember = await workspaceMemberRepository.findOneOrFail({
where: { id: workspaceMemberId },
});
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', 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.CREATED,
events: [
{
recordId: newMessageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
after: newMessageChannel,
},
},
],
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] },
if (isCalendarEnabled) {
const newCalendarChannel = await calendarChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
handle,
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: 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')) {
const messageChannels = await messageChannelRepository.find({

View File

@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { ConnectedAccountProvider } from 'twenty-shared/types';
import { EntityManager, Repository } from 'typeorm';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
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 { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import {
@ -107,226 +108,232 @@ export class MicrosoftAPIsService {
const scopes = getMicrosoftApisOauthScopes();
await workspaceDataSource.transaction(async (manager: EntityManager) => {
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: [
await workspaceDataSource.transaction(
async (manager: WorkspaceEntityManager) => {
if (!existingAccountId) {
const newConnectedAccount = await connectedAccountRepository.save(
{
recordId: newConnectedAccount.id,
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,
id: newOrExistingConnectedAccountId,
handle,
visibility:
calendarVisibility ||
CalendarChannelVisibility.SHARE_EVERYTHING,
provider: ConnectedAccountProvider.MICROSOFT,
accessToken: input.accessToken,
refreshToken: input.refreshToken,
accountOwnerId: workspaceMemberId,
scopes,
},
{},
manager,
);
const calendarChannelMetadata =
const connectedAccountMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'calendarChannel', workspaceId },
where: { nameSingular: 'connectedAccount', workspaceId },
});
this.workspaceEventEmitter.emitDatabaseBatchEvent({
objectMetadataNameSingular: 'calendarChannel',
objectMetadataNameSingular: 'connectedAccount',
action: DatabaseEventAction.CREATED,
events: [
{
recordId: newCalendarChannel.id,
objectMetadata: calendarChannelMetadata,
recordId: newConnectedAccount.id,
objectMetadata: connectedAccountMetadata,
properties: {
after: newCalendarChannel,
after: newConnectedAccount,
},
},
],
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: [
const newMessageChannel = await messageChannelRepository.save(
{
recordId: newOrExistingConnectedAccountId,
objectMetadata: connectedAccountMetadata,
properties: {
before: connectedAccount,
after: {
...connectedAccount,
...updatedConnectedAccount.raw[0],
},
},
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
type: MessageChannelType.EMAIL,
handle,
visibility:
messageVisibility || MessageChannelVisibility.SHARE_EVERYTHING,
syncStatus: MessageChannelSyncStatus.ONGOING,
},
],
workspaceId,
});
const workspaceMemberRepository =
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
workspaceId,
'workspaceMember',
{},
manager,
);
const workspaceMember = await workspaceMemberRepository.findOneOrFail({
where: { id: workspaceMemberId },
});
await messageFolderRepository.save(
{
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(
userId,
workspaceId,
newOrExistingConnectedAccountId,
);
const messageChannelMetadata =
await this.objectMetadataRepository.findOneOrFail({
where: { nameSingular: 'messageChannel', workspaceId },
});
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.CREATED,
events: [
{
recordId: newMessageChannel.id,
objectMetadata: messageChannelMetadata,
properties: {
after: newMessageChannel,
},
},
],
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] },
if (
this.twentyConfigService.get('CALENDAR_PROVIDER_MICROSOFT_ENABLED')
) {
const newCalendarChannel = await calendarChannelRepository.save(
{
id: v4(),
connectedAccountId: newOrExistingConnectedAccountId,
handle,
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')) {
const messageChannels = await messageChannelRepository.find({

View File

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

View File

@ -26,6 +26,7 @@ import {
validateStringAgainstInjections,
} 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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
@Injectable()
@ -79,7 +80,7 @@ export class RemoteServerService<T extends RemoteServerType> {
}
return this.metadataDataSource.transaction(
async (entityManager: EntityManager) => {
async (entityManager: WorkspaceEntityManager) => {
const createdRemoteServer = entityManager.create(
RemoteServerEntity,
remoteServerToCreate,

View File

@ -1,7 +1,8 @@
import { Injectable } from '@nestjs/common';
import { capitalize, isDefined } from 'twenty-shared/utils';
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';
@ -60,12 +61,13 @@ export class SeederService {
const schemaName =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const workspaceDataSource =
const workspaceDataSource: DataSource =
await this.workspaceDataSourceService.connectToWorkspaceDataSource(
workspaceId,
);
const entityManager = workspaceDataSource.createEntityManager();
const entityManager: EntityManager =
workspaceDataSource.createEntityManager();
const filteredFieldMetadataSeeds = objectMetadataSeed.fields.filter(
(field) =>

View File

@ -5,12 +5,14 @@ import {
EntityTarget,
ObjectLiteral,
QueryRunner,
ReplicationMode,
} from 'typeorm';
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 { 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';
export class WorkspaceDataSource extends DataSource {
@ -31,10 +33,10 @@ export class WorkspaceDataSource extends DataSource {
) {
super(options);
this.internalContext = internalContext;
// Recreate manager after internalContext has been initialized
this.manager = this.createEntityManager();
this.featureFlagMap = featureFlagMap;
this.featureFlagMapVersion = featureFlagMapVersion;
// Recreate manager after internalContext has been initialized
this.manager = this.createEntityManager();
this.rolesPermissionsVersion = rolesPermissionsVersion;
this.permissionsPerRoleId = permissionsPerRoleId;
}
@ -65,6 +67,17 @@ export class WorkspaceDataSource extends DataSource {
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) {
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 = (
expressionMap: QueryExpressionMap,
objectRecordsPermissions: ObjectRecordsPermissions,
objectMetadataMaps: ObjectMetadataMaps,
shouldBypassPermissionChecks: boolean,
) => {
if (shouldBypassPermissionChecks) {
return;
}
const { mainEntity, operationType } =
getTargetEntityAndOperationType(expressionMap);
export type OperationType =
| 'select'
| 'insert'
| 'update'
| 'delete'
| 'restore'
| 'soft-delete';
export const validateOperationIsPermittedOrThrow = ({
entityName,
operationType,
objectRecordsPermissions,
objectMetadataMaps,
}: {
entityName: string;
operationType: OperationType;
objectRecordsPermissions: ObjectRecordsPermissions;
objectMetadataMaps: ObjectMetadataMaps;
}) => {
const objectMetadataIdForEntity =
objectMetadataMaps.idByNameSingular[mainEntity];
objectMetadataMaps.idByNameSingular[entityName];
const objectMetadataIsSystem =
objectMetadataMaps.byId[objectMetadataIdForEntity]?.isSystem === true;
@ -41,7 +47,7 @@ export const validateQueryIsPermittedOrThrow = (
return;
}
const permissionsForEntity = objectRecordsPermissions[mainEntity];
const permissionsForEntity = objectRecordsPermissions[entityName];
switch (operationType) {
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 { 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 { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-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 { 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 { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-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 { 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 { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
@ -50,6 +50,48 @@ export class WorkspaceSelectQueryBuilder<
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(

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

View File

@ -2,9 +2,9 @@ import { Injectable } from '@nestjs/common';
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 { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@Injectable()
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';
export const seedCompanyWithDemoData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.company`, [
'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 { EntityManager } from 'typeorm';
import { v4 } from 'uuid';
const tableName = 'opportunity';
@ -33,7 +33,7 @@ const generateOpportunities = (companies) => {
};
export const seedOpportunityWithDemoData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
const companiesWithPeople = await entityManager?.query(
@ -46,7 +46,9 @@ export const seedOpportunityWithDemoData = async (
const opportunities = generateOpportunities(companiesWithPeople);
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.${tableName}`, [
'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';
export const seedPersonWithDemoData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
const companies = await entityManager?.query(
@ -27,7 +27,9 @@ export const seedPersonWithDemoData = async (
}));
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.person`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { SOURCE_LOCALE } from 'twenty-shared/translations';
export const DEMO_SEED_WORKSPACE_MEMBER_IDS = {
@ -10,11 +10,13 @@ export const DEMO_SEED_WORKSPACE_MEMBER_IDS = {
};
export const seedWorkspaceMemberWithDemoData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.workspaceMember`, [
'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 { 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 { 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';
@ -28,7 +29,7 @@ export const seedWorkspaceWithDemoData = async (
}, {});
await workspaceDataSource.transaction(
async (entityManager: EntityManager) => {
async (entityManager: WorkspaceEntityManager) => {
await seedCompanyWithDemoData(entityManager, schemaName);
await seedPersonWithDemoData(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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
export const AIRBNB_ID = 'c776ee49-f608-4a77-8cc8-6fe96ae1e43f';
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 companyPrefillData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.company`, [
'id',

View File

@ -1,10 +1,10 @@
import { EntityManager } from 'typeorm';
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';
export const createWorkspaceViews = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
viewDefinitions: ViewDefinition[],
) => {
@ -14,7 +14,9 @@ export const createWorkspaceViews = async (
}));
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.view`, [
'id',
@ -64,7 +66,9 @@ export const createWorkspaceViews = async (
for (const viewDefinition of viewDefinitionsWithId) {
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.viewField`, [
'fieldMetadataId',
@ -89,7 +93,9 @@ export const createWorkspaceViews = async (
if (viewDefinition.filters && viewDefinition.filters.length > 0) {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.viewFilter`, [
'fieldMetadataId',
@ -116,7 +122,9 @@ export const createWorkspaceViews = async (
viewDefinition.groups.length > 0
) {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.viewGroup`, [
'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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import {
AIRBNB_ID,
FIGMA_ID,
@ -11,11 +10,13 @@ import {
// FixMe: Is this file a duplicate of src/database/typeorm-seeds/workspace/people.ts
export const personPrefillData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
) => {
await entityManager
.createQueryBuilder()
.createQueryBuilder(undefined, undefined, undefined, {
shouldBypassPermissionChecks: true,
})
.insert()
.into(`${schemaName}.person`, [
'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 { 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 { 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';
@ -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';
export const seedViewWithDemoData = async (
entityManager: EntityManager,
entityManager: WorkspaceEntityManager,
schemaName: string,
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 { 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 { companyPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/company';
import { personPrefillData } from 'src/engine/workspace-manager/standard-objects-prefill-data/person';
@ -33,28 +34,30 @@ export const standardObjectsPrefillData = async (
return acc;
}, {});
workspaceDataSource.transaction(async (entityManager: EntityManager) => {
await companyPrefillData(entityManager, schemaName);
await personPrefillData(entityManager, schemaName);
const viewDefinitionsWithId = await seedViewWithDemoData(
entityManager,
schemaName,
objectMetadataMap,
);
workspaceDataSource.transaction(
async (entityManager: WorkspaceEntityManager) => {
await companyPrefillData(entityManager, schemaName);
await personPrefillData(entityManager, schemaName);
const viewDefinitionsWithId = await seedViewWithDemoData(
entityManager,
schemaName,
objectMetadataMap,
);
await seedWorkspaceFavorites(
viewDefinitionsWithId
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataMap,
),
)
.map((view) => view.id),
entityManager,
schemaName,
);
});
await seedWorkspaceFavorites(
viewDefinitionsWithId
.filter(
(view) =>
view.key === 'INDEX' &&
shouldSeedWorkspaceFavorite(
view.objectMetadataId,
objectMetadataMap,
),
)
.map((view) => view.id),
entityManager,
schemaName,
);
},
);
};

View File

@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
@ -14,7 +12,6 @@ export class BlocklistRepository {
public async getById(
id: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<BlocklistWorkspaceEntity | null> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -24,7 +21,6 @@ export class BlocklistRepository {
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "id" = $1`,
[id],
workspaceId,
transactionManager,
);
if (!blocklistItems || blocklistItems.length === 0) {
@ -37,7 +33,6 @@ export class BlocklistRepository {
public async getByWorkspaceMemberId(
workspaceMemberId: string,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<BlocklistWorkspaceEntity[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -46,7 +41,6 @@ export class BlocklistRepository {
`SELECT * FROM ${dataSourceSchema}."blocklist" WHERE "workspaceMemberId" = $1`,
[workspaceMemberId],
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 { 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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-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 { 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();
await workspaceDataSource?.transaction(async (transactionManager) => {
await calendarEventRepository.save(
eventsToSave.map(
(calendarEvent) =>
({
id: calendarEvent.id,
iCalUID: calendarEvent.iCalUID,
title: calendarEvent.title,
description: calendarEvent.description,
startsAt: calendarEvent.startsAt,
endsAt: calendarEvent.endsAt,
location: calendarEvent.location,
isFullDay: calendarEvent.isFullDay,
isCanceled: calendarEvent.isCanceled,
conferenceSolution: calendarEvent.conferenceSolution,
conferenceLink: {
primaryLinkLabel: calendarEvent.conferenceLinkLabel,
primaryLinkUrl: calendarEvent.conferenceLinkUrl,
},
externalCreatedAt: calendarEvent.externalCreatedAt,
externalUpdatedAt: calendarEvent.externalUpdatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
),
{},
transactionManager,
);
await workspaceDataSource?.transaction(
async (transactionManager: WorkspaceEntityManager) => {
await calendarEventRepository.save(
eventsToSave.map(
(calendarEvent) =>
({
id: calendarEvent.id,
iCalUID: calendarEvent.iCalUID,
title: calendarEvent.title,
description: calendarEvent.description,
startsAt: calendarEvent.startsAt,
endsAt: calendarEvent.endsAt,
location: calendarEvent.location,
isFullDay: calendarEvent.isFullDay,
isCanceled: calendarEvent.isCanceled,
conferenceSolution: calendarEvent.conferenceSolution,
conferenceLink: {
primaryLinkLabel: calendarEvent.conferenceLinkLabel,
primaryLinkUrl: calendarEvent.conferenceLinkUrl,
},
externalCreatedAt: calendarEvent.externalCreatedAt,
externalUpdatedAt: calendarEvent.externalUpdatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
),
{},
transactionManager,
);
await calendarEventRepository.save(
eventsToUpdate.map(
(calendarEvent) =>
({
id: calendarEvent.id,
iCalUID: calendarEvent.iCalUID,
title: calendarEvent.title,
description: calendarEvent.description,
startsAt: calendarEvent.startsAt,
endsAt: calendarEvent.endsAt,
location: calendarEvent.location,
isFullDay: calendarEvent.isFullDay,
isCanceled: calendarEvent.isCanceled,
conferenceSolution: calendarEvent.conferenceSolution,
conferenceLink: {
primaryLinkLabel: calendarEvent.conferenceLinkLabel,
primaryLinkUrl: calendarEvent.conferenceLinkUrl,
},
externalCreatedAt: calendarEvent.externalCreatedAt,
externalUpdatedAt: calendarEvent.externalUpdatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
),
{},
transactionManager,
);
await calendarEventRepository.save(
eventsToUpdate.map(
(calendarEvent) =>
({
id: calendarEvent.id,
iCalUID: calendarEvent.iCalUID,
title: calendarEvent.title,
description: calendarEvent.description,
startsAt: calendarEvent.startsAt,
endsAt: calendarEvent.endsAt,
location: calendarEvent.location,
isFullDay: calendarEvent.isFullDay,
isCanceled: calendarEvent.isCanceled,
conferenceSolution: calendarEvent.conferenceSolution,
conferenceLink: {
primaryLinkLabel: calendarEvent.conferenceLinkLabel,
primaryLinkUrl: calendarEvent.conferenceLinkUrl,
},
externalCreatedAt: calendarEvent.externalCreatedAt,
externalUpdatedAt: calendarEvent.externalUpdatedAt,
}) satisfies DeepPartial<CalendarEventWorkspaceEntity>,
),
{},
transactionManager,
);
await calendarChannelEventAssociationRepository.save(
calendarChannelEventAssociationsToSave,
{},
transactionManager,
);
await calendarChannelEventAssociationRepository.save(
calendarChannelEventAssociationsToSave,
{},
transactionManager,
);
await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants(
participantsToSave,
participantsToUpdate,
transactionManager,
);
});
await this.calendarEventParticipantService.upsertAndDeleteCalendarEventParticipants(
participantsToSave,
participantsToUpdate,
transactionManager,
);
},
);
if (calendarChannel.isContactAutoCreationEnabled) {
await this.messageQueueService.add<CreateCompanyAndContactJobData>(

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,8 @@
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 { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
@ -39,7 +40,7 @@ export class MatchParticipantService<
public async matchParticipants(
participants: ParticipantWorkspaceEntity[],
objectMetadataName: 'messageParticipant' | 'calendarEventParticipant',
transactionManager?: EntityManager,
transactionManager?: WorkspaceEntityManager,
) {
const participantRepository =
await this.getParticipantRepository(objectMetadataName);

View File

@ -1,7 +1,8 @@
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 { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.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();
await workspaceDataSource.transaction(async (transactionManager) => {
await deleteUsingPagination(
workspaceId,
500,
async (
limit: number,
offset: number,
workspaceId: string,
transactionManager: EntityManager,
) => {
const nonAssociatedMessages = await messageRepository.find(
{
where: {
messageChannelMessageAssociations: {
id: IsNull(),
await workspaceDataSource.transaction(
async (transactionManager: WorkspaceEntityManager) => {
await deleteUsingPagination(
workspaceId,
500,
async (
limit: number,
offset: number,
_workspaceId: string,
transactionManager: WorkspaceEntityManager,
) => {
const nonAssociatedMessages = await messageRepository.find(
{
where: {
messageChannelMessageAssociations: {
id: IsNull(),
},
},
take: limit,
skip: offset,
relations: ['messageChannelMessageAssociations'],
},
take: limit,
skip: offset,
relations: ['messageChannelMessageAssociations'],
},
transactionManager,
);
transactionManager,
);
return nonAssociatedMessages.map(({ id }) => id);
},
async (
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
) => {
await messageRepository.delete(ids, transactionManager);
},
transactionManager,
);
return nonAssociatedMessages.map(({ id }) => id);
},
async (
ids: string[],
workspaceId: string,
transactionManager?: WorkspaceEntityManager,
) => {
await messageRepository.delete(ids, transactionManager);
},
transactionManager,
);
await deleteUsingPagination(
workspaceId,
500,
async (
limit: number,
offset: number,
workspaceId: string,
transactionManager?: EntityManager,
) => {
const orphanThreads = await messageThreadRepository.find(
{
where: {
messages: {
id: IsNull(),
await deleteUsingPagination(
workspaceId,
500,
async (
limit: number,
offset: number,
_workspaceId: string,
transactionManager?: WorkspaceEntityManager,
) => {
const orphanThreads = await messageThreadRepository.find(
{
where: {
messages: {
id: IsNull(),
},
},
take: limit,
skip: offset,
},
take: limit,
skip: offset,
},
transactionManager,
);
transactionManager,
);
return orphanThreads.map(({ id }) => id);
},
async (
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
) => {
await messageThreadRepository.delete(ids, transactionManager);
},
transactionManager,
);
});
return orphanThreads.map(({ id }) => id);
},
async (
ids: string[],
_workspaceId: string,
transactionManager?: WorkspaceEntityManager,
) => {
await messageThreadRepository.delete(ids, 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 (
workspaceId: string,
@ -7,14 +7,14 @@ export const deleteUsingPagination = async (
limit: number,
offset: number,
workspaceId: string,
transactionManager?: EntityManager,
transactionManager?: WorkspaceEntityManager,
) => Promise<string[]>,
deleter: (
ids: string[],
workspaceId: string,
transactionManager?: EntityManager,
transactionManager?: WorkspaceEntityManager,
) => Promise<void>,
transactionManager?: EntityManager,
transactionManager?: WorkspaceEntityManager,
) => {
let hasMoreData = true;

View File

@ -1,8 +1,8 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
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 { 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';
@ -16,7 +16,7 @@ export class MessagingMessageService {
public async saveMessagesWithinTransaction(
messages: MessageWithParticipants[],
messageChannelId: string,
transactionManager: EntityManager,
transactionManager: WorkspaceEntityManager,
): Promise<{
createdMessages: Partial<MessageWorkspaceEntity>[];
messageExternalIdsAndIdsMap: Map<string, string>;

View File

@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
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 { 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 { 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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
@ -55,7 +56,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
const createdMessagesWithParticipants =
await workspaceDataSource?.transaction(
async (transactionManager: EntityManager) => {
async (transactionManager: WorkspaceEntityManager) => {
const { messageExternalIdsAndIdsMap, createdMessages } =
await this.messageService.saveMessagesWithinTransaction(
messagesToSave,

View File

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

View File

@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
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';
@ -159,7 +157,6 @@ export class TimelineActivityRepository {
linkedObjectMetadataId: string | undefined;
}[],
workspaceId: string,
transactionManager?: EntityManager,
) {
if (activities.length === 0) {
return;
@ -191,7 +188,6 @@ export class TimelineActivityRepository {
])
.flat(),
workspaceId,
transactionManager,
);
}
}

View File

@ -1,14 +1,13 @@
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 { WorkflowEventListenerWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-event-listener.workspace-entity';
import {
AutomatedTriggerType,
AutomatedTriggerSettings,
AutomatedTriggerType,
WorkflowAutomatedTriggerWorkspaceEntity,
} 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()
export class AutomatedTriggerWorkspaceService {
@ -21,7 +20,7 @@ export class AutomatedTriggerWorkspaceService {
settings,
}: {
workflowId: string;
manager: EntityManager;
manager: WorkspaceEntityManager;
type: AutomatedTriggerType;
settings: AutomatedTriggerSettings;
}) {
@ -68,7 +67,7 @@ export class AutomatedTriggerWorkspaceService {
manager,
}: {
workflowId: string;
manager: EntityManager;
manager: WorkspaceEntityManager;
}) {
// Todo: remove workflowEventListenerRepository updates when data are migrated to workflowAutomatedTrigger
const workflowEventListenerRepository =

View File

@ -1,15 +1,17 @@
import { Injectable } from '@nestjs/common';
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 { 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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
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 {
WorkflowVersionStatus,
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 { 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 { AutomatedTriggerWorkspaceService } from 'src/modules/workflow/workflow-trigger/automated-trigger/automated-trigger.workspace-service';
import {
WorkflowTriggerException,
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 { computeCronPatternFromSchedule } from 'src/modules/workflow/workflow-trigger/utils/compute-cron-pattern-from-schedule';
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()
export class WorkflowTriggerWorkspaceService {
@ -174,7 +175,7 @@ export class WorkflowTriggerWorkspaceService {
workflowVersion: WorkflowVersionWorkspaceEntity,
workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
if (
workflow.lastPublishedVersionId &&
@ -207,7 +208,7 @@ export class WorkflowTriggerWorkspaceService {
private async performDeactivationSteps(
workflowVersionId: string,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
const workflowVersionNullable = await workflowVersionRepository.findOne({
where: { id: workflowVersionId },
@ -234,7 +235,7 @@ export class WorkflowTriggerWorkspaceService {
private async setActiveVersionStatus(
workflowVersion: WorkflowVersionWorkspaceEntity,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
const activeWorkflowVersions = await workflowVersionRepository.find(
{
@ -269,7 +270,7 @@ export class WorkflowTriggerWorkspaceService {
private async setDeactivatedVersionStatus(
workflowVersion: WorkflowVersionWorkspaceEntity,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
if (workflowVersion.status !== WorkflowVersionStatus.ACTIVE) {
throw new WorkflowTriggerException(
@ -296,7 +297,7 @@ export class WorkflowTriggerWorkspaceService {
newPublishedVersionId: string,
workflowRepository: WorkspaceRepository<WorkflowWorkspaceEntity>,
workflowVersionRepository: WorkspaceRepository<WorkflowVersionWorkspaceEntity>,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
if (workflow.lastPublishedVersionId === newPublishedVersionId) {
return;
@ -319,7 +320,7 @@ export class WorkflowTriggerWorkspaceService {
private async enableTrigger(
workflowVersion: WorkflowVersionWorkspaceEntity,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
assertWorkflowVersionTriggerIsDefined(workflowVersion);
@ -359,7 +360,7 @@ export class WorkflowTriggerWorkspaceService {
private async disableTrigger(
workflowVersion: WorkflowVersionWorkspaceEntity,
manager: EntityManager,
manager: WorkspaceEntityManager,
) {
assertWorkflowVersionTriggerIsDefined(workflowVersion);

View File

@ -1,7 +1,5 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
@ -50,7 +48,6 @@ export class WorkspaceMemberRepository {
public async getAllByWorkspaceId(
workspaceId: string,
transactionManager?: EntityManager,
): Promise<WorkspaceMemberWorkspaceEntity[]> {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
@ -60,7 +57,6 @@ export class WorkspaceMemberRepository {
`SELECT * FROM ${dataSourceSchema}."workspaceMember"`,
[],
workspaceId,
transactionManager,
);
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 { 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 { 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 { PermissionsExceptionMessage } from 'src/engine/metadata-modules/permissions/permissions.exception';
@ -48,79 +51,79 @@ describe('createOneObjectRecordsPermissions', () => {
});
});
// describe('permissions V2 enabled', () => {
// beforeAll(async () => {
// const enablePermissionsQuery = updateFeatureFlagFactory(
// SEED_APPLE_WORKSPACE_ID,
// 'IsPermissionsV2Enabled',
// true,
// );
describe('permissions V2 enabled', () => {
beforeAll(async () => {
const enablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
true,
);
// await makeGraphqlAPIRequest(enablePermissionsQuery);
// });
await makeGraphqlAPIRequest(enablePermissionsQuery);
});
// afterAll(async () => {
// const disablePermissionsQuery = updateFeatureFlagFactory(
// SEED_APPLE_WORKSPACE_ID,
// 'IsPermissionsV2Enabled',
// false,
// );
afterAll(async () => {
const disablePermissionsQuery = updateFeatureFlagFactory(
SEED_APPLE_WORKSPACE_ID,
'IsPermissionsV2Enabled',
false,
);
// await makeGraphqlAPIRequest(disablePermissionsQuery);
// });
await makeGraphqlAPIRequest(disablePermissionsQuery);
});
// it('should throw a permission error when user does not have permission (guest role)', async () => {
// const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS,
// data: {
// id: randomUUID(),
// },
// });
it('should throw a permission error when user does not have permission (guest role)', async () => {
const graphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: randomUUID(),
},
});
// const response =
// await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
const response =
await makeGraphqlAPIRequestWithGuestRole(graphqlOperation);
// expect(response.body.data).toStrictEqual({ createPerson: null });
// expect(response.body.errors).toBeDefined();
// expect(response.body.errors[0].message).toBe(
// PermissionsExceptionMessage.PERMISSION_DENIED,
// );
// expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
// });
expect(response.body.data).toStrictEqual({ createPerson: null });
expect(response.body.errors).toBeDefined();
expect(response.body.errors[0].message).toBe(
PermissionsExceptionMessage.PERMISSION_DENIED,
);
expect(response.body.errors[0].extensions.code).toBe(ErrorCode.FORBIDDEN);
});
// it('should create an object record when user has permission (admin role)', async () => {
// const personId = randomUUID();
// const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS,
// data: {
// id: personId,
// },
// });
it('should create an object record when user has permission (admin role)', async () => {
const personId = randomUUID();
const graphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
// const response = await makeGraphqlAPIRequest(graphqlOperation);
const response = await makeGraphqlAPIRequest(graphqlOperation);
// expect(response.body.data).toBeDefined();
// expect(response.body.data.createPerson).toBeDefined();
// expect(response.body.data.createPerson.id).toBe(personId);
// });
expect(response.body.data).toBeDefined();
expect(response.body.data.createPerson).toBeDefined();
expect(response.body.data.createPerson.id).toBe(personId);
});
// it('should create an object record when executed by api key', async () => {
// const personId = randomUUID();
// const graphqlOperation = createOneOperationFactory({
// objectMetadataSingularName: 'person',
// gqlFields: PERSON_GQL_FIELDS,
// data: {
// id: personId,
// },
// });
it('should create an object record when executed by api key', async () => {
const personId = randomUUID();
const graphqlOperation = createOneOperationFactory({
objectMetadataSingularName: 'person',
gqlFields: PERSON_GQL_FIELDS,
data: {
id: personId,
},
});
// const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
const response = await makeGraphqlAPIRequestWithApiKey(graphqlOperation);
// expect(response.body.data).toBeDefined();
// expect(response.body.data.createPerson).toBeDefined();
// expect(response.body.data.createPerson.id).toBe(personId);
// });
// });
expect(response.body.data).toBeDefined();
expect(response.body.data.createPerson).toBeDefined();
expect(response.body.data.createPerson.id).toBe(personId);
});
});
});