Add db event emitter in twenty orm (#13167)
## Context Add an eventEmitter instance to twenty datasources so we can emit DB events. Add input and output formatting to twenty orm (formatData, formatResult) Those 2 elements simplified existing logic when we interact with the ORM, input will be formatted by the ORM so we can directly use field-like structure instead of column-like. The output will be formatted, for builder queries it will be in `result.generatedMaps` where `result.raw` preserves the previous column-like structure. Important change: We now have an authContext that we can pass when we get a repository, this will be used for the different events emitted in the ORM. We also removed the caching for repositories as it was not scaling well and not necessary imho Note: An upcoming PR should handle the onDelete: cascade behavior where we send DESTROY events in cascade when there is an onDelete: CASCADE on the FK. --------- Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
@ -1,5 +1,5 @@
|
|||||||
diff --git a/node_modules/typeorm/common/PickKeysByType.d.ts b/node_modules/typeorm/common/PickKeysByType.d.ts
|
diff --git a/common/PickKeysByType.d.ts b/common/PickKeysByType.d.ts
|
||||||
index 55ad347..1a8a184 100644
|
index 55ad347..4288c06 100644
|
||||||
--- a/common/PickKeysByType.d.ts
|
--- a/common/PickKeysByType.d.ts
|
||||||
+++ b/common/PickKeysByType.d.ts
|
+++ b/common/PickKeysByType.d.ts
|
||||||
@@ -1,6 +1,6 @@
|
@@ -1,6 +1,6 @@
|
||||||
@ -12,3 +12,35 @@ index 55ad347..1a8a184 100644
|
|||||||
+export type PickKeysByType<T, U> = string & {
|
+export type PickKeysByType<T, U> = string & {
|
||||||
+ [P in keyof T]: Exclude<T[P], null> extends U ? P : never;
|
+ [P in keyof T]: Exclude<T[P], null> extends U ? P : never;
|
||||||
+}[keyof T];
|
+}[keyof T];
|
||||||
|
diff --git a/query-builder/result/DeleteResult.d.ts b/query-builder/result/DeleteResult.d.ts
|
||||||
|
index 9c98830..d0578f9 100644
|
||||||
|
--- a/query-builder/result/DeleteResult.d.ts
|
||||||
|
+++ b/query-builder/result/DeleteResult.d.ts
|
||||||
|
@@ -13,4 +13,9 @@ export declare class DeleteResult {
|
||||||
|
* Not all drivers support this
|
||||||
|
*/
|
||||||
|
affected?: number | null;
|
||||||
|
+ /**
|
||||||
|
+ * Generated values returned by a database.
|
||||||
|
+ * Has entity-like structure (not just column database name and values).
|
||||||
|
+ */
|
||||||
|
+ generatedMaps: ObjectLiteral[];
|
||||||
|
}
|
||||||
|
diff --git a/query-builder/result/DeleteResult.js b/query-builder/result/DeleteResult.js
|
||||||
|
index 6519c11..0bb344a 100644
|
||||||
|
--- a/query-builder/result/DeleteResult.js
|
||||||
|
+++ b/query-builder/result/DeleteResult.js
|
||||||
|
@@ -5,6 +5,13 @@ exports.DeleteResult = void 0;
|
||||||
|
* Result object returned by DeleteQueryBuilder execution.
|
||||||
|
*/
|
||||||
|
class DeleteResult {
|
||||||
|
+ constructor() {
|
||||||
|
+ /**
|
||||||
|
+ * Generated values returned by a database.
|
||||||
|
+ * Has entity-like structure (not just column database name and values).
|
||||||
|
+ */
|
||||||
|
+ this.generatedMaps = [];
|
||||||
|
+ }
|
||||||
|
static from(queryResult) {
|
||||||
|
const result = new this();
|
||||||
|
result.raw = queryResult.records;
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/g
|
|||||||
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
|
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
|
||||||
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
|
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
|
||||||
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
|
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
import { WorkspaceQueryHookModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/workspace-query-hook.module';
|
||||||
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
import { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
||||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||||
@ -49,7 +48,6 @@ const graphqlQueryResolvers = [
|
|||||||
UserRoleModule,
|
UserRoleModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
ApiEventEmitterService,
|
|
||||||
ProcessNestedRelationsHelper,
|
ProcessNestedRelationsHelper,
|
||||||
ProcessNestedRelationsV2Helper,
|
ProcessNestedRelationsV2Helper,
|
||||||
ProcessAggregateHelper,
|
ProcessAggregateHelper,
|
||||||
|
|||||||
@ -20,7 +20,6 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met
|
|||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
import { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
import { isFieldMetadataInterfaceOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -180,8 +179,6 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
: 'id',
|
: 'id',
|
||||||
ids: relationIds,
|
ids: relationIds,
|
||||||
limit: limit * parentObjectRecords.length,
|
limit: limit * parentObjectRecords.length,
|
||||||
objectMetadataMaps,
|
|
||||||
targetObjectMetadata,
|
|
||||||
aggregate,
|
aggregate,
|
||||||
sourceFieldName,
|
sourceFieldName,
|
||||||
});
|
});
|
||||||
@ -286,8 +283,6 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
column,
|
column,
|
||||||
ids,
|
ids,
|
||||||
limit,
|
limit,
|
||||||
objectMetadataMaps,
|
|
||||||
targetObjectMetadata,
|
|
||||||
aggregate,
|
aggregate,
|
||||||
sourceFieldName,
|
sourceFieldName,
|
||||||
}: {
|
}: {
|
||||||
@ -297,8 +292,6 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
ids: any[];
|
ids: any[];
|
||||||
limit: number;
|
limit: number;
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
|
||||||
targetObjectMetadata: ObjectMetadataItemWithFieldMaps;
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
aggregate: Record<string, any>;
|
aggregate: Record<string, any>;
|
||||||
sourceFieldName: string;
|
sourceFieldName: string;
|
||||||
@ -359,13 +352,7 @@ export class ProcessNestedRelationsV2Helper {
|
|||||||
.take(limit)
|
.take(limit)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const relationResults = formatResult<ObjectRecord[]>(
|
return { relationResults: result, relationAggregatedFieldsResult };
|
||||||
result,
|
|
||||||
targetObjectMetadata,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
return { relationResults, relationAggregatedFieldsResult };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private assignRelationResults({
|
private assignRelationResults({
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import { OBJECTS_WITH_SETTINGS_PERMISSIONS_REQUIREMENTS } from 'src/engine/api/g
|
|||||||
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
import { GraphqlQuerySelectedFieldsResult } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-selected-fields/graphql-selected-fields.parser';
|
||||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
import { ProcessNestedRelationsHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/process-nested-relations.helper';
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
|
import { QueryResultGettersFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-result-getters/query-result-getters.factory';
|
||||||
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
import { QueryRunnerArgsFactory } from 'src/engine/api/graphql/workspace-query-runner/factories/query-runner-args.factory';
|
||||||
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
import { workspaceQueryRunnerGraphqlApiExceptionHandler } from 'src/engine/api/graphql/workspace-query-runner/utils/workspace-query-runner-graphql-api-exception-handler.util';
|
||||||
@ -66,8 +65,6 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
@Inject()
|
@Inject()
|
||||||
protected readonly queryResultGettersFactory: QueryResultGettersFactory;
|
protected readonly queryResultGettersFactory: QueryResultGettersFactory;
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly apiEventEmitterService: ApiEventEmitterService;
|
|
||||||
@Inject()
|
|
||||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
||||||
@ -128,6 +125,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
|||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks,
|
||||||
roleId,
|
roleId,
|
||||||
|
authContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
const graphqlQueryParser = new GraphqlQueryParser(
|
const graphqlQueryParser = new GraphqlQueryParser(
|
||||||
|
|||||||
@ -19,15 +19,11 @@ import {
|
|||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
|
||||||
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -48,7 +44,6 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
executionArgs,
|
executionArgs,
|
||||||
objectRecords,
|
objectRecords,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldBypassPermissionChecks = executionArgs.isExecutedByApiKey;
|
const shouldBypassPermissionChecks = executionArgs.isExecutedByApiKey;
|
||||||
@ -105,21 +100,15 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
|
|
||||||
await this.processRecordsToUpdate({
|
await this.processRecordsToUpdate({
|
||||||
partialRecordsToUpdate: recordsToUpdate,
|
partialRecordsToUpdate: recordsToUpdate,
|
||||||
existingRecords,
|
|
||||||
repository: executionArgs.repository,
|
repository: executionArgs.repository,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: executionArgs.options.objectMetadataMaps,
|
|
||||||
result,
|
result,
|
||||||
authContext: executionArgs.options.authContext,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.processRecordsToInsert({
|
await this.processRecordsToInsert({
|
||||||
recordsToInsert,
|
recordsToInsert,
|
||||||
repository: executionArgs.repository,
|
repository: executionArgs.repository,
|
||||||
result,
|
result,
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps: executionArgs.options.objectMetadataMaps,
|
|
||||||
authContext: executionArgs.options.authContext,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@ -273,20 +262,14 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
|
|
||||||
private async processRecordsToUpdate({
|
private async processRecordsToUpdate({
|
||||||
partialRecordsToUpdate,
|
partialRecordsToUpdate,
|
||||||
existingRecords,
|
|
||||||
repository,
|
repository,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps,
|
|
||||||
result,
|
result,
|
||||||
authContext,
|
|
||||||
}: {
|
}: {
|
||||||
partialRecordsToUpdate: Partial<ObjectRecord>[];
|
partialRecordsToUpdate: Partial<ObjectRecord>[];
|
||||||
existingRecords: Partial<ObjectRecord>[];
|
|
||||||
repository: WorkspaceRepository<ObjectLiteral>;
|
repository: WorkspaceRepository<ObjectLiteral>;
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
|
||||||
result: InsertResult;
|
result: InsertResult;
|
||||||
authContext: AuthContext;
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
for (const partialRecordToUpdate of partialRecordsToUpdate) {
|
for (const partialRecordToUpdate of partialRecordsToUpdate) {
|
||||||
const recordId = partialRecordToUpdate.id as string;
|
const recordId = partialRecordToUpdate.id as string;
|
||||||
@ -298,101 +281,38 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedPartialRecordToUpdate = formatData(
|
await repository.update(
|
||||||
|
recordId,
|
||||||
partialRecordToUpdateWithoutCreatedByUpdate,
|
partialRecordToUpdateWithoutCreatedByUpdate,
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: we should align update and insert
|
|
||||||
// For insert, formating is done in the server
|
|
||||||
// While for update, formatting is done at the resolver level
|
|
||||||
await repository.update(recordId, formattedPartialRecordToUpdate);
|
|
||||||
|
|
||||||
result.identifiers.push({ id: recordId });
|
result.identifiers.push({ id: recordId });
|
||||||
result.generatedMaps.push({ id: recordId });
|
result.generatedMaps.push({ id: recordId });
|
||||||
|
|
||||||
const [updatedRecord] = await repository.find({
|
|
||||||
where: { id: recordId },
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!isDefined(updatedRecord)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const record = formatResult<ObjectRecord>(
|
|
||||||
updatedRecord,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingRecord = formatResult<ObjectRecord>(
|
|
||||||
existingRecords.find((record) => record.id === recordId),
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
|
||||||
existingRecords: structuredClone([existingRecord]),
|
|
||||||
records: structuredClone([record]),
|
|
||||||
updatedFields: Object.keys(formattedPartialRecordToUpdate),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem:
|
|
||||||
getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processRecordsToInsert({
|
private async processRecordsToInsert({
|
||||||
recordsToInsert,
|
recordsToInsert,
|
||||||
repository,
|
repository,
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
result,
|
result,
|
||||||
authContext,
|
|
||||||
}: {
|
}: {
|
||||||
recordsToInsert: Partial<ObjectRecord>[];
|
recordsToInsert: Partial<ObjectRecord>[];
|
||||||
repository: WorkspaceRepository<ObjectLiteral>;
|
repository: WorkspaceRepository<ObjectLiteral>;
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
|
||||||
result: InsertResult;
|
result: InsertResult;
|
||||||
authContext: AuthContext;
|
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const formattedInsertedRecords: ObjectRecord[] = [];
|
|
||||||
|
|
||||||
if (recordsToInsert.length > 0) {
|
if (recordsToInsert.length > 0) {
|
||||||
const insertResult = await repository.insert(recordsToInsert);
|
const insertResult = await repository.insert(recordsToInsert);
|
||||||
|
|
||||||
result.identifiers.push(...insertResult.identifiers);
|
result.identifiers.push(...insertResult.identifiers);
|
||||||
result.generatedMaps.push(...insertResult.generatedMaps);
|
result.generatedMaps.push(...insertResult.generatedMaps);
|
||||||
result.raw.push(...insertResult.raw);
|
result.raw.push(...insertResult.raw);
|
||||||
|
|
||||||
formattedInsertedRecords.push(
|
|
||||||
...insertResult.raw.map((record: ObjectRecord) =>
|
|
||||||
formatResult<ObjectRecord>(
|
|
||||||
record,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
|
||||||
records: structuredClone(formattedInsertedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchUpsertedRecords(
|
private async fetchUpsertedRecords(
|
||||||
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
|
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
|
||||||
objectRecords: InsertResult,
|
objectRecords: InsertResult,
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataMaps: ObjectMetadataMaps,
|
|
||||||
): Promise<ObjectRecord[]> {
|
): Promise<ObjectRecord[]> {
|
||||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
@ -404,7 +324,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedUpsertedRecords = await queryBuilder
|
const upsertedRecords = await queryBuilder
|
||||||
.setFindOptions({
|
.setFindOptions({
|
||||||
select: columnsToSelect,
|
select: columnsToSelect,
|
||||||
})
|
})
|
||||||
@ -414,11 +334,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
.take(QUERY_MAX_RECORDS)
|
.take(QUERY_MAX_RECORDS)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return formatResult<ObjectRecord[]>(
|
return upsertedRecords as ObjectRecord[];
|
||||||
nonFormattedUpsertedRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async processNestedRelationsIfNeeded(
|
private async processNestedRelationsIfNeeded(
|
||||||
|
|||||||
@ -15,8 +15,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -48,7 +46,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedUpsertedRecords = await queryBuilder
|
const upsertedRecords = (await queryBuilder
|
||||||
.setFindOptions({
|
.setFindOptions({
|
||||||
select: columnsToSelect,
|
select: columnsToSelect,
|
||||||
})
|
})
|
||||||
@ -56,21 +54,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
||||||
})
|
})
|
||||||
.take(QUERY_MAX_RECORDS)
|
.take(QUERY_MAX_RECORDS)
|
||||||
.getMany();
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
const upsertedRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedUpsertedRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
|
||||||
records: structuredClone(upsertedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
|
|||||||
@ -14,8 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -52,30 +50,17 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
const deletedObjectRecords = await queryBuilder
|
||||||
.softDelete()
|
.softDelete()
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedDeletedRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedDeletedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents({
|
|
||||||
records: structuredClone(formattedDeletedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: formattedDeletedRecords,
|
parentObjectRecords:
|
||||||
|
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
@ -89,7 +74,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
|||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||||
|
|
||||||
return formattedDeletedRecords.map((record: ObjectRecord) =>
|
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||||
typeORMObjectRecordsParser.processRecord({
|
typeORMObjectRecordsParser.processRecord({
|
||||||
objectRecord: record,
|
objectRecord: record,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
|||||||
@ -18,8 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -44,34 +42,20 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
const deletedObjectRecords = await queryBuilder
|
||||||
.softDelete()
|
.softDelete()
|
||||||
.where({ id: executionArgs.args.id })
|
.where({ id: executionArgs.args.id })
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedDeletedRecords = formatResult<ObjectRecord[]>(
|
if (deletedObjectRecords.generatedMaps.length === 0) {
|
||||||
nonFormattedDeletedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (formattedDeletedRecords.length === 0) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedRecord = formattedDeletedRecords[0];
|
const deletedRecord = deletedObjectRecords.generatedMaps[0] as ObjectRecord;
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDeletedEvents({
|
|
||||||
records: structuredClone(formattedDeletedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
|
|||||||
@ -12,8 +12,6 @@ import { DestroyManyResolverArgs } from 'src/engine/api/graphql/workspace-resolv
|
|||||||
|
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -50,30 +48,17 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
const deletedObjectRecords = await queryBuilder
|
||||||
.delete()
|
.delete()
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const deletedRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedDeletedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents({
|
|
||||||
records: structuredClone(deletedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: deletedRecords,
|
parentObjectRecords:
|
||||||
|
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
@ -87,7 +72,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
|||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||||
|
|
||||||
return deletedRecords.map((record: ObjectRecord) =>
|
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||||
typeORMObjectRecordsParser.processRecord({
|
typeORMObjectRecordsParser.processRecord({
|
||||||
objectRecord: record,
|
objectRecord: record,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
|||||||
@ -16,8 +16,6 @@ import {
|
|||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -42,38 +40,25 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
const deletedObjectRecords = await queryBuilder
|
||||||
.delete()
|
.delete()
|
||||||
.where({ id: executionArgs.args.id })
|
.where({ id: executionArgs.args.id })
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
if (!nonFormattedDeletedObjectRecords.affected) {
|
if (!deletedObjectRecords.affected) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedDeletedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents({
|
|
||||||
records: structuredClone(deletedRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: deletedRecords,
|
parentObjectRecords:
|
||||||
|
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
@ -88,7 +73,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
|||||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||||
|
|
||||||
return typeORMObjectRecordsParser.processRecord({
|
return typeORMObjectRecordsParser.processRecord({
|
||||||
objectRecord: deletedRecords[0],
|
objectRecord: deletedObjectRecords.generatedMaps[0] as ObjectRecord,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
take: 1,
|
take: 1,
|
||||||
totalCount: 1,
|
totalCount: 1,
|
||||||
|
|||||||
@ -24,8 +24,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
import { buildColumnsToSelect } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-select';
|
||||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -67,20 +65,11 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||||
|
|
||||||
if (executionArgs.args.ids) {
|
if (executionArgs.args.ids) {
|
||||||
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
objectRecords = (await existingRecordsQueryBuilder
|
||||||
.where({ id: In(executionArgs.args.ids) })
|
.where({ id: In(executionArgs.args.ids) })
|
||||||
.getMany()) as ObjectRecord[];
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
objectRecords = formatResult(
|
|
||||||
nonFormattedObjectRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
|
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
|
||||||
objectRecords = formatData(
|
objectRecords = executionArgs.args.data;
|
||||||
executionArgs.args.data,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
||||||
@ -120,18 +109,12 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
|||||||
duplicateConditions,
|
duplicateConditions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const nonFormattedDuplicates = (await duplicateRecordsQueryBuilder
|
const duplicates = (await duplicateRecordsQueryBuilder
|
||||||
.setFindOptions({
|
.setFindOptions({
|
||||||
select: columnsToSelect,
|
select: columnsToSelect,
|
||||||
})
|
})
|
||||||
.getMany()) as ObjectRecord[];
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
const duplicates = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedDuplicates,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
return typeORMObjectRecordsParser.createConnection({
|
return typeORMObjectRecordsParser.createConnection({
|
||||||
objectRecords: duplicates,
|
objectRecords: duplicates,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
|||||||
@ -29,7 +29,6 @@ import {
|
|||||||
getPaginationInfo,
|
getPaginationInfo,
|
||||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||||
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
|
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -127,18 +126,12 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedObjectRecords = await queryBuilder
|
const objectRecords = (await queryBuilder
|
||||||
.setFindOptions({
|
.setFindOptions({
|
||||||
select: columnsToSelect,
|
select: columnsToSelect,
|
||||||
})
|
})
|
||||||
.take(limit + 1)
|
.take(limit + 1)
|
||||||
.getMany();
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
const objectRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedObjectRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||||
objectRecords,
|
objectRecords,
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import {
|
|||||||
WorkspaceQueryRunnerException,
|
WorkspaceQueryRunnerException,
|
||||||
WorkspaceQueryRunnerExceptionCode,
|
WorkspaceQueryRunnerExceptionCode,
|
||||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -59,18 +58,12 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedObjectRecord = await queryBuilder
|
const objectRecord = await queryBuilder
|
||||||
.setFindOptions({
|
.setFindOptions({
|
||||||
select: columnsToSelect,
|
select: columnsToSelect,
|
||||||
})
|
})
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
const objectRecord = formatResult<ObjectRecord>(
|
|
||||||
nonFormattedObjectRecord,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!objectRecord) {
|
if (!objectRecord) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
@ -78,7 +71,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectRecords = [objectRecord];
|
const objectRecords = [objectRecord] as ObjectRecord[];
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
|
|||||||
@ -14,8 +14,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -52,30 +50,17 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedRestoredObjectRecords = await queryBuilder
|
const restoredObjectRecords = await queryBuilder
|
||||||
.restore()
|
.restore()
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedRestoredRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedRestoredObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitRestoreEvents({
|
|
||||||
records: structuredClone(formattedRestoredRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: formattedRestoredRecords,
|
parentObjectRecords:
|
||||||
|
restoredObjectRecords.generatedMaps as ObjectRecord[],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
@ -89,7 +74,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
|||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||||
|
|
||||||
return formattedRestoredRecords.map((record: ObjectRecord) =>
|
return restoredObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||||
typeORMObjectRecordsParser.processRecord({
|
typeORMObjectRecordsParser.processRecord({
|
||||||
objectRecord: record,
|
objectRecord: record,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
|||||||
@ -18,8 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -44,34 +42,21 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedRestoredObjectRecords = await queryBuilder
|
const restoredObjectRecords = await queryBuilder
|
||||||
.restore()
|
.restore()
|
||||||
.where({ id: executionArgs.args.id })
|
.where({ id: executionArgs.args.id })
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedRestoredRecords = formatResult<ObjectRecord[]>(
|
if (restoredObjectRecords.generatedMaps.length === 0) {
|
||||||
nonFormattedRestoredObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (formattedRestoredRecords.length === 0) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const restoredRecord = formattedRestoredRecords[0];
|
const restoredRecord = restoredObjectRecords
|
||||||
|
.generatedMaps[0] as ObjectRecord;
|
||||||
this.apiEventEmitterService.emitRestoreEvents({
|
|
||||||
records: structuredClone(formattedRestoredRecords),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import isEmpty from 'lodash.isempty';
|
|
||||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -11,17 +10,10 @@ import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/int
|
|||||||
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
|
||||||
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
import { UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||||
|
|
||||||
import {
|
|
||||||
GraphqlQueryRunnerException,
|
|
||||||
GraphqlQueryRunnerExceptionCode,
|
|
||||||
} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception';
|
|
||||||
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
|
||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
import { computeTableName } from 'src/engine/utils/compute-table-name.util';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -41,29 +33,6 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const existingRecordsBuilder = queryBuilder.clone();
|
|
||||||
|
|
||||||
executionArgs.graphqlQueryParser.applyFilterToBuilder(
|
|
||||||
existingRecordsBuilder,
|
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
|
||||||
executionArgs.args.filter,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingRecords = await existingRecordsBuilder.getMany();
|
|
||||||
|
|
||||||
const formattedExistingRecords = formatResult<ObjectRecord[]>(
|
|
||||||
existingRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isEmpty(formattedExistingRecords)) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
|
||||||
'Records not found',
|
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableName = computeTableName(
|
const tableName = computeTableName(
|
||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
objectMetadataItemWithFieldMaps.isCustom,
|
objectMetadataItemWithFieldMaps.isCustom,
|
||||||
@ -75,46 +44,24 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
executionArgs.args.filter,
|
executionArgs.args.filter,
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = formatData(
|
|
||||||
executionArgs.args.data,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const columnsToReturn = buildColumnsToReturn({
|
const columnsToReturn = buildColumnsToReturn({
|
||||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
const updatedObjectRecords = await queryBuilder
|
||||||
.update(data)
|
.update()
|
||||||
|
.set(executionArgs.args.data)
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedUpdatedRecords = formatResult<ObjectRecord[]>(
|
|
||||||
nonFormattedUpdatedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
|
||||||
existingRecords: structuredClone(formattedExistingRecords),
|
|
||||||
records: structuredClone(formattedUpdatedRecords),
|
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: [
|
parentObjectRecords:
|
||||||
...formattedExistingRecords,
|
updatedObjectRecords.generatedMaps as ObjectRecord[],
|
||||||
...formattedUpdatedRecords,
|
|
||||||
],
|
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
@ -128,7 +75,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
|||||||
const typeORMObjectRecordsParser =
|
const typeORMObjectRecordsParser =
|
||||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||||
|
|
||||||
return formattedUpdatedRecords.map((record: ObjectRecord) =>
|
return updatedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||||
typeORMObjectRecordsParser.processRecord({
|
typeORMObjectRecordsParser.processRecord({
|
||||||
objectRecord: record,
|
objectRecord: record,
|
||||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import isEmpty from 'lodash.isempty';
|
|
||||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -19,9 +18,6 @@ import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/g
|
|||||||
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
import { buildColumnsToReturn } from 'src/engine/api/graphql/graphql-query-runner/utils/build-columns-to-return';
|
||||||
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
import { assertIsValidUuid } from 'src/engine/api/graphql/workspace-query-runner/utils/assert-is-valid-uuid.util';
|
||||||
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService<
|
export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService<
|
||||||
@ -40,73 +36,33 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
|||||||
objectMetadataItemWithFieldMaps.nameSingular,
|
objectMetadataItemWithFieldMaps.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
const data = formatData(
|
|
||||||
executionArgs.args.data,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const existingRecordBuilder = queryBuilder.clone();
|
|
||||||
|
|
||||||
const existingRecords = (await existingRecordBuilder
|
|
||||||
.where({ id: executionArgs.args.id })
|
|
||||||
.getMany()) as ObjectRecord[];
|
|
||||||
|
|
||||||
const formattedExistingRecords = formatResult<ObjectRecord[]>(
|
|
||||||
existingRecords,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isEmpty(formattedExistingRecords)) {
|
|
||||||
throw new GraphqlQueryRunnerException(
|
|
||||||
'Record not found',
|
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnsToReturn = buildColumnsToReturn({
|
const columnsToReturn = buildColumnsToReturn({
|
||||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
const updatedObjectRecords = await queryBuilder
|
||||||
.update(data)
|
.update()
|
||||||
|
.set(executionArgs.args.data)
|
||||||
.where({ id: executionArgs.args.id })
|
.where({ id: executionArgs.args.id })
|
||||||
.returning(columnsToReturn)
|
.returning(columnsToReturn)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
const formattedUpdatedRecords = formatResult<ObjectRecord[]>(
|
const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord;
|
||||||
nonFormattedUpdatedObjectRecords.raw,
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (formattedUpdatedRecords.length === 0) {
|
if (!updatedRecord) {
|
||||||
throw new GraphqlQueryRunnerException(
|
throw new GraphqlQueryRunnerException(
|
||||||
'Record not found',
|
'Record not found',
|
||||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatedRecord = formattedUpdatedRecords[0];
|
|
||||||
const existingRecord = formattedExistingRecords[0];
|
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
|
||||||
existingRecords: structuredClone(formattedExistingRecords),
|
|
||||||
records: structuredClone(formattedUpdatedRecords),
|
|
||||||
updatedFields: Object.keys(executionArgs.args.data),
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadataItemWithFieldMaps,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||||
await this.processNestedRelationsHelper.processNestedRelations({
|
await this.processNestedRelationsHelper.processNestedRelations({
|
||||||
objectMetadataMaps,
|
objectMetadataMaps,
|
||||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||||
parentObjectRecords: [existingRecord, updatedRecord],
|
parentObjectRecords: [updatedRecord],
|
||||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||||
limit: QUERY_MAX_RECORDS,
|
limit: QUERY_MAX_RECORDS,
|
||||||
authContext,
|
authContext,
|
||||||
|
|||||||
@ -1,188 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
|
||||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class ApiEventEmitterService {
|
|
||||||
constructor(private readonly workspaceEventEmitter: WorkspaceEventEmitter) {}
|
|
||||||
|
|
||||||
public emitCreateEvents<T extends ObjectRecord>({
|
|
||||||
records,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
records: T[];
|
|
||||||
authContext: AuthContext;
|
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
}): void {
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: records.map((record) => ({
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before: null,
|
|
||||||
after: record,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId: authContext.workspace?.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitUpdateEvents<T extends ObjectRecord>({
|
|
||||||
existingRecords,
|
|
||||||
records,
|
|
||||||
updatedFields,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
existingRecords: T[];
|
|
||||||
records: T[];
|
|
||||||
updatedFields: string[];
|
|
||||||
authContext: AuthContext;
|
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
}): void {
|
|
||||||
const workspace = authContext.workspace;
|
|
||||||
|
|
||||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
|
||||||
|
|
||||||
const mappedExistingRecords = existingRecords.reduce(
|
|
||||||
(acc, { id, ...record }) => ({
|
|
||||||
...acc,
|
|
||||||
[id]: record,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: records.map((record) => {
|
|
||||||
// @ts-expect-error legacy noImplicitAny
|
|
||||||
const before = mappedExistingRecords[record.id];
|
|
||||||
const after = record;
|
|
||||||
const diff = objectRecordChangedValues(
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
updatedFields,
|
|
||||||
objectMetadataItem,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before,
|
|
||||||
after,
|
|
||||||
updatedFields,
|
|
||||||
diff,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitDeletedEvents<T extends ObjectRecord>({
|
|
||||||
records,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
records: T[];
|
|
||||||
authContext: AuthContext;
|
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
}): void {
|
|
||||||
const workspace = authContext.workspace;
|
|
||||||
|
|
||||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
action: DatabaseEventAction.DELETED,
|
|
||||||
events: records.map((record) => {
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before: record,
|
|
||||||
after: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitRestoreEvents<T extends ObjectRecord>({
|
|
||||||
records,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
records: T[];
|
|
||||||
authContext: AuthContext;
|
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
}): void {
|
|
||||||
const workspace = authContext.workspace;
|
|
||||||
|
|
||||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
action: DatabaseEventAction.RESTORED,
|
|
||||||
events: records.map((record) => {
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before: null,
|
|
||||||
after: record,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public emitDestroyEvents<T extends ObjectRecord>({
|
|
||||||
records,
|
|
||||||
authContext,
|
|
||||||
objectMetadataItem,
|
|
||||||
}: {
|
|
||||||
records: T[];
|
|
||||||
authContext: AuthContext;
|
|
||||||
objectMetadataItem: ObjectMetadataInterface;
|
|
||||||
}): void {
|
|
||||||
const workspace = authContext.workspace;
|
|
||||||
|
|
||||||
workspaceValidator.assertIsDefinedOrThrow(workspace);
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectMetadataItem.nameSingular,
|
|
||||||
action: DatabaseEventAction.DESTROYED,
|
|
||||||
events: records.map((record) => {
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: record.id,
|
|
||||||
objectMetadata: objectMetadataItem,
|
|
||||||
properties: {
|
|
||||||
before: record,
|
|
||||||
after: null,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -9,8 +9,6 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
|
|
||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
||||||
async handle(request: Request) {
|
async handle(request: Request) {
|
||||||
@ -60,14 +58,6 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler {
|
|||||||
|
|
||||||
const createdRecords = await repository.save(recordsToCreate);
|
const createdRecords = await repository.save(recordsToCreate);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
|
||||||
records: createdRecords,
|
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadata.objectMetadataMapItem,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
recordIds: createdRecords.map((record) => record.id),
|
recordIds: createdRecords.map((record) => record.id),
|
||||||
repository,
|
repository,
|
||||||
|
|||||||
@ -9,8 +9,6 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
|
|
||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
||||||
async handle(request: Request) {
|
async handle(request: Request) {
|
||||||
@ -43,14 +41,6 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler {
|
|||||||
|
|
||||||
const createdRecord = await repository.save(recordToCreate);
|
const createdRecord = await repository.save(recordToCreate);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitCreateEvents({
|
|
||||||
records: [createdRecord],
|
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadata.objectMetadataMapItem,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
recordIds: [createdRecord.id],
|
recordIds: [createdRecord.id],
|
||||||
repository,
|
repository,
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { Request } from 'express';
|
|||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
||||||
@ -24,14 +23,6 @@ export class RestApiDeleteOneHandler extends RestApiBaseHandler {
|
|||||||
|
|
||||||
await repository.delete(recordId);
|
await repository.delete(recordId);
|
||||||
|
|
||||||
this.apiEventEmitterService.emitDestroyEvents({
|
|
||||||
records: [recordToDelete],
|
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadata.objectMetadataMapItem,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
return this.formatResult({
|
return this.formatResult({
|
||||||
operation: 'delete',
|
operation: 'delete',
|
||||||
objectNameSingular: objectMetadata.objectMetadataMapItem.nameSingular,
|
objectNameSingular: objectMetadata.objectMetadataMapItem.nameSingular,
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import {
|
|||||||
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
import { buildDuplicateConditions } from 'src/engine/api/utils/build-duplicate-conditions.utils';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
||||||
@ -28,15 +27,9 @@ export class RestApiFindDuplicatesHandler extends RestApiBaseHandler {
|
|||||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||||
|
|
||||||
if (request.body.ids) {
|
if (request.body.ids) {
|
||||||
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
objectRecords = (await existingRecordsQueryBuilder
|
||||||
.where({ id: In(request.body.ids) })
|
.where({ id: In(request.body.ids) })
|
||||||
.getMany()) as ObjectRecord[];
|
.getMany()) as ObjectRecord[];
|
||||||
|
|
||||||
objectRecords = formatResult(
|
|
||||||
nonFormattedObjectRecords,
|
|
||||||
objectMetadataItemWithFieldsMaps,
|
|
||||||
objectMetadata.objectMetadataMaps,
|
|
||||||
);
|
|
||||||
} else if (request.body.data && !isEmpty(request.body.data)) {
|
} else if (request.body.data && !isEmpty(request.body.data)) {
|
||||||
objectRecords = request.body.data;
|
objectRecords = request.body.data;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { isDefined } from 'twenty-shared/utils';
|
|||||||
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
|
||||||
|
|
||||||
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
|
||||||
import { getObjectMetadataFromObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/utils/get-object-metadata-from-object-metadata-Item-with-field-maps';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
||||||
@ -38,16 +37,6 @@ export class RestApiUpdateOneHandler extends RestApiBaseHandler {
|
|||||||
...overriddenBody,
|
...overriddenBody,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.apiEventEmitterService.emitUpdateEvents({
|
|
||||||
existingRecords: [recordToUpdate],
|
|
||||||
records: [updatedRecord],
|
|
||||||
updatedFields: Object.keys(request.body),
|
|
||||||
authContext: this.getAuthContextFromRequest(request),
|
|
||||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
|
||||||
objectMetadata.objectMetadataMapItem,
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const records = await this.getRecord({
|
const records = await this.getRecord({
|
||||||
recordIds: [updatedRecord.id],
|
recordIds: [updatedRecord.id],
|
||||||
repository,
|
repository,
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import {
|
|||||||
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
import { encodeCursor } from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||||
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
import { CoreQueryBuilderFactory } from 'src/engine/api/rest/core/query-builder/core-query-builder.factory';
|
||||||
import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory';
|
import { GetVariablesFactory } from 'src/engine/api/rest/core/query-builder/factories/get-variables.factory';
|
||||||
@ -80,8 +79,6 @@ export abstract class RestApiBaseHandler {
|
|||||||
@Inject()
|
@Inject()
|
||||||
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService;
|
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService;
|
||||||
@Inject()
|
@Inject()
|
||||||
protected readonly apiEventEmitterService: ApiEventEmitterService;
|
|
||||||
@Inject()
|
|
||||||
protected readonly createdByFromAuthContextService: CreatedByFromAuthContextService;
|
protected readonly createdByFromAuthContextService: CreatedByFromAuthContextService;
|
||||||
|
|
||||||
protected abstract handle(
|
protected abstract handle(
|
||||||
|
|||||||
@ -1,25 +1,24 @@
|
|||||||
import { Module } from '@nestjs/common';
|
|
||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
|
||||||
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
|
|
||||||
import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler';
|
|
||||||
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
|
||||||
import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler';
|
|
||||||
import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler';
|
|
||||||
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
|
||||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
|
||||||
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
|
||||||
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
|
||||||
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
import { RestApiCoreController } from 'src/engine/api/rest/core/controllers/rest-api-core.controller';
|
||||||
|
import { RestApiCreateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-many.handler';
|
||||||
|
import { RestApiCreateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-one.handler';
|
||||||
|
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
|
||||||
|
import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler';
|
||||||
|
import { RestApiFindManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-many.handler';
|
||||||
|
import { RestApiFindOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-one.handler';
|
||||||
|
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
|
||||||
|
import { CoreQueryBuilderModule } from 'src/engine/api/rest/core/query-builder/core-query-builder.module';
|
||||||
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
|
import { coreQueryBuilderFactories } from 'src/engine/api/rest/core/query-builder/factories/factories';
|
||||||
import { RestApiCoreService } from 'src/engine/api/rest/core/services/rest-api-core.service';
|
import { RestApiCoreService } from 'src/engine/api/rest/core/services/rest-api-core.service';
|
||||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||||
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
|
||||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
|
||||||
import { RestApiCreateManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-create-many.handler';
|
|
||||||
import { RestApiFindDuplicatesHandler } from 'src/engine/api/rest/core/handlers/rest-api-find-duplicates.handler';
|
|
||||||
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
|
||||||
|
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||||
|
import { RecordTransformerModule } from 'src/engine/core-modules/record-transformer/record-transformer.module';
|
||||||
|
import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.module';
|
||||||
|
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||||
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
const restApiCoreResolvers = [
|
const restApiCoreResolvers = [
|
||||||
RestApiDeleteOneHandler,
|
RestApiDeleteOneHandler,
|
||||||
@ -46,7 +45,6 @@ const restApiCoreResolvers = [
|
|||||||
providers: [
|
providers: [
|
||||||
RestApiService,
|
RestApiService,
|
||||||
RestApiCoreService,
|
RestApiCoreService,
|
||||||
ApiEventEmitterService,
|
|
||||||
...coreQueryBuilderFactories,
|
...coreQueryBuilderFactories,
|
||||||
...restApiCoreResolvers,
|
...restApiCoreResolvers,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import {
|
|||||||
|
|
||||||
import { settings } from 'src/engine/constants/settings';
|
import { settings } from 'src/engine/constants/settings';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { getCompositeFieldMetadataMap } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { getCompositeFieldMetadataMap } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
|
||||||
export const buildDuplicateConditions = (
|
export const buildDuplicateConditions = (
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import {
|
|||||||
Not,
|
Not,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import {
|
import {
|
||||||
generateBulkDeleteToolSchema,
|
generateBulkDeleteToolSchema,
|
||||||
generateFindOneToolSchema,
|
generateFindOneToolSchema,
|
||||||
@ -25,13 +24,11 @@ import { isWorkflowRelatedObject } from 'src/engine/metadata-modules/agent/utils
|
|||||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ToolService {
|
export class ToolService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly objectMetadataService: ObjectMetadataService,
|
private readonly objectMetadataService: ObjectMetadataService,
|
||||||
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
protected readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||||
) {}
|
) {}
|
||||||
@ -378,13 +375,6 @@ export class ToolService {
|
|||||||
|
|
||||||
const createdRecord = await repository.save(parameters);
|
const createdRecord = await repository.save(parameters);
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
records: [createdRecord],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
record: createdRecord,
|
record: createdRecord,
|
||||||
@ -449,14 +439,6 @@ export class ToolService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
records: [updatedRecord],
|
|
||||||
workspaceId,
|
|
||||||
beforeRecords: [existingRecord],
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
record: updatedRecord,
|
record: updatedRecord,
|
||||||
@ -509,13 +491,6 @@ export class ToolService {
|
|||||||
|
|
||||||
await repository.softDelete(id);
|
await repository.softDelete(id);
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.DELETED,
|
|
||||||
records: [existingRecord],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Successfully soft deleted ${objectName}`,
|
message: `Successfully soft deleted ${objectName}`,
|
||||||
@ -567,13 +542,6 @@ export class ToolService {
|
|||||||
|
|
||||||
await repository.remove(existingRecord);
|
await repository.remove(existingRecord);
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.DESTROYED,
|
|
||||||
records: [existingRecord],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
message: `Successfully destroyed ${objectName}`,
|
message: `Successfully destroyed ${objectName}`,
|
||||||
@ -636,13 +604,6 @@ export class ToolService {
|
|||||||
|
|
||||||
await repository.softDelete({ id: { in: recordIds } });
|
await repository.softDelete({ id: { in: recordIds } });
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.DELETED,
|
|
||||||
records: existingRecords,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
count: existingRecords.length,
|
count: existingRecords.length,
|
||||||
@ -706,13 +667,6 @@ export class ToolService {
|
|||||||
|
|
||||||
await repository.delete({ id: { in: recordIds } });
|
await repository.delete({ id: { in: recordIds } });
|
||||||
|
|
||||||
await this.emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action: DatabaseEventAction.DESTROYED,
|
|
||||||
records: existingRecords,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
count: existingRecords.length,
|
count: existingRecords.length,
|
||||||
@ -726,53 +680,4 @@ export class ToolService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async emitDatabaseEvent({
|
|
||||||
objectName,
|
|
||||||
action,
|
|
||||||
records,
|
|
||||||
workspaceId,
|
|
||||||
beforeRecords,
|
|
||||||
}: {
|
|
||||||
objectName: string;
|
|
||||||
action: DatabaseEventAction;
|
|
||||||
records: Record<string, unknown>[];
|
|
||||||
workspaceId: string;
|
|
||||||
beforeRecords?: Record<string, unknown>[];
|
|
||||||
}) {
|
|
||||||
const objectMetadata =
|
|
||||||
await this.objectMetadataService.findOneWithinWorkspace(workspaceId, {
|
|
||||||
where: {
|
|
||||||
nameSingular: objectName,
|
|
||||||
isActive: true,
|
|
||||||
},
|
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: objectName,
|
|
||||||
action,
|
|
||||||
events: records.map((record) => {
|
|
||||||
const beforeRecord = beforeRecords?.find((r) => r.id === record.id);
|
|
||||||
|
|
||||||
return {
|
|
||||||
recordId: record.id as string,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: beforeRecord || undefined,
|
|
||||||
after:
|
|
||||||
action === DatabaseEventAction.DELETED ||
|
|
||||||
action === DatabaseEventAction.DESTROYED
|
|
||||||
? undefined
|
|
||||||
: (record as Record<string, unknown>),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
CalendarChannelWorkspaceEntity,
|
CalendarChannelWorkspaceEntity,
|
||||||
@ -26,9 +21,6 @@ export type CreateCalendarChannelInput = {
|
|||||||
export class CreateCalendarChannelService {
|
export class CreateCalendarChannelService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createCalendarChannel(
|
async createCalendarChannel(
|
||||||
@ -60,26 +52,6 @@ export class CreateCalendarChannelService {
|
|||||||
manager,
|
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newCalendarChannel.id;
|
return newCalendarChannel.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
|
|
||||||
export type CreateConnectedAccountInput = {
|
export type CreateConnectedAccountInput = {
|
||||||
@ -27,9 +22,6 @@ export type CreateConnectedAccountInput = {
|
|||||||
export class CreateConnectedAccountService {
|
export class CreateConnectedAccountService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createConnectedAccount(
|
async createConnectedAccount(
|
||||||
@ -53,7 +45,7 @@ export class CreateConnectedAccountService {
|
|||||||
'connectedAccount',
|
'connectedAccount',
|
||||||
);
|
);
|
||||||
|
|
||||||
const newConnectedAccount = await connectedAccountRepository.save(
|
await connectedAccountRepository.save(
|
||||||
{
|
{
|
||||||
id: connectedAccountId,
|
id: connectedAccountId,
|
||||||
handle,
|
handle,
|
||||||
@ -66,25 +58,5 @@ export class CreateConnectedAccountService {
|
|||||||
{},
|
{},
|
||||||
manager,
|
manager,
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectedAccountMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'connectedAccount',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: newConnectedAccount.id,
|
|
||||||
objectMetadata: connectedAccountMetadata,
|
|
||||||
properties: {
|
|
||||||
after: newConnectedAccount,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
MessageChannelSyncStatus,
|
MessageChannelSyncStatus,
|
||||||
MessageChannelType,
|
MessageChannelType,
|
||||||
@ -28,9 +23,6 @@ export type CreateMessageChannelInput = {
|
|||||||
export class CreateMessageChannelService {
|
export class CreateMessageChannelService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async createMessageChannel(
|
async createMessageChannel(
|
||||||
@ -64,26 +56,6 @@ export class CreateMessageChannelService {
|
|||||||
manager,
|
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newMessageChannel.id;
|
return newMessageChannel.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,7 +16,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q
|
|||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
CalendarChannelSyncStage,
|
CalendarChannelSyncStage,
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
@ -161,12 +160,6 @@ describe('GoogleAPIsService', () => {
|
|||||||
removeAccountToReconnect: jest.fn(),
|
removeAccountToReconnect: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||||
useValue: mockMessageQueueService,
|
useValue: mockMessageQueueService,
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q
|
|||||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
CalendarChannelSyncStage,
|
CalendarChannelSyncStage,
|
||||||
CalendarChannelVisibility,
|
CalendarChannelVisibility,
|
||||||
@ -166,12 +165,6 @@ describe('MicrosoftAPIsService', () => {
|
|||||||
removeAccountToReconnect: jest.fn(),
|
removeAccountToReconnect: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: getQueueToken(MessageQueue.messagingQueue),
|
provide: getQueueToken(MessageQueue.messagingQueue),
|
||||||
useValue: mockMessageQueueService,
|
useValue: mockMessageQueueService,
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
CalendarChannelSyncStage,
|
CalendarChannelSyncStage,
|
||||||
CalendarChannelWorkspaceEntity,
|
CalendarChannelWorkspaceEntity,
|
||||||
@ -23,9 +17,6 @@ export type ResetCalendarChannelsInput = {
|
|||||||
export class ResetCalendarChannelService {
|
export class ResetCalendarChannelService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async resetCalendarChannels(
|
async resetCalendarChannels(
|
||||||
@ -39,11 +30,7 @@ export class ResetCalendarChannelService {
|
|||||||
'calendarChannel',
|
'calendarChannel',
|
||||||
);
|
);
|
||||||
|
|
||||||
const calendarChannels = await calendarChannelRepository.find({
|
await calendarChannelRepository.update(
|
||||||
where: { connectedAccountId },
|
|
||||||
});
|
|
||||||
|
|
||||||
const calendarChannelUpdates = await calendarChannelRepository.update(
|
|
||||||
{
|
{
|
||||||
connectedAccountId,
|
connectedAccountId,
|
||||||
},
|
},
|
||||||
@ -57,28 +44,6 @@ export class ResetCalendarChannelService {
|
|||||||
manager,
|
manager,
|
||||||
);
|
);
|
||||||
|
|
||||||
const calendarChannelMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: { nameSingular: 'calendarChannel', workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'calendarChannel',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: calendarChannels.map((calendarChannel) => ({
|
|
||||||
recordId: calendarChannel.id,
|
|
||||||
objectMetadata: calendarChannelMetadata,
|
|
||||||
properties: {
|
|
||||||
before: calendarChannel,
|
|
||||||
after: {
|
|
||||||
...calendarChannel,
|
|
||||||
...calendarChannelUpdates.raw[0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
MessageChannelSyncStage,
|
MessageChannelSyncStage,
|
||||||
MessageChannelSyncStatus,
|
MessageChannelSyncStatus,
|
||||||
@ -24,9 +18,6 @@ export type ResetMessageChannelsInput = {
|
|||||||
export class ResetMessageChannelService {
|
export class ResetMessageChannelService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async resetMessageChannels(input: ResetMessageChannelsInput): Promise<void> {
|
async resetMessageChannels(input: ResetMessageChannelsInput): Promise<void> {
|
||||||
@ -38,11 +29,7 @@ export class ResetMessageChannelService {
|
|||||||
'messageChannel',
|
'messageChannel',
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageChannels = await messageChannelRepository.find({
|
await messageChannelRepository.update(
|
||||||
where: { connectedAccountId },
|
|
||||||
});
|
|
||||||
|
|
||||||
const messageChannelUpdates = await messageChannelRepository.update(
|
|
||||||
{
|
{
|
||||||
connectedAccountId,
|
connectedAccountId,
|
||||||
},
|
},
|
||||||
@ -55,25 +42,6 @@ export class ResetMessageChannelService {
|
|||||||
manager,
|
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,
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
|
|
||||||
export type UpdateConnectedAccountOnReconnectInput = {
|
export type UpdateConnectedAccountOnReconnectInput = {
|
||||||
@ -24,9 +18,6 @@ export type UpdateConnectedAccountOnReconnectInput = {
|
|||||||
export class UpdateConnectedAccountOnReconnectService {
|
export class UpdateConnectedAccountOnReconnectService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async updateConnectedAccountOnReconnect(
|
async updateConnectedAccountOnReconnect(
|
||||||
@ -38,7 +29,6 @@ export class UpdateConnectedAccountOnReconnectService {
|
|||||||
accessToken,
|
accessToken,
|
||||||
refreshToken,
|
refreshToken,
|
||||||
scopes,
|
scopes,
|
||||||
connectedAccount,
|
|
||||||
manager,
|
manager,
|
||||||
} = input;
|
} = input;
|
||||||
|
|
||||||
@ -48,7 +38,7 @@ export class UpdateConnectedAccountOnReconnectService {
|
|||||||
'connectedAccount',
|
'connectedAccount',
|
||||||
);
|
);
|
||||||
|
|
||||||
const updatedConnectedAccount = await connectedAccountRepository.update(
|
await connectedAccountRepository.update(
|
||||||
{
|
{
|
||||||
id: connectedAccountId,
|
id: connectedAccountId,
|
||||||
},
|
},
|
||||||
@ -60,29 +50,5 @@ export class UpdateConnectedAccountOnReconnectService {
|
|||||||
},
|
},
|
||||||
manager,
|
manager,
|
||||||
);
|
);
|
||||||
|
|
||||||
const connectedAccountMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: { nameSingular: 'connectedAccount', workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'connectedAccount',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: connectedAccountId,
|
|
||||||
objectMetadata: connectedAccountMetadata,
|
|
||||||
properties: {
|
|
||||||
before: connectedAccount,
|
|
||||||
after: {
|
|
||||||
...connectedAccount,
|
|
||||||
...updatedConnectedAccount.raw[0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,7 @@
|
|||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
|
||||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
const mockObjectMetadata: ObjectMetadataInterface = {
|
const mockObjectMetadata: ObjectMetadataItemWithFieldMaps = {
|
||||||
id: '1',
|
id: '1',
|
||||||
icon: 'Icon123',
|
icon: 'Icon123',
|
||||||
nameSingular: 'Object',
|
nameSingular: 'Object',
|
||||||
@ -12,14 +11,16 @@ const mockObjectMetadata: ObjectMetadataInterface = {
|
|||||||
description: 'Test object metadata',
|
description: 'Test object metadata',
|
||||||
targetTableName: 'test_table',
|
targetTableName: 'test_table',
|
||||||
workspaceId: '1',
|
workspaceId: '1',
|
||||||
fields: [],
|
fieldsById: {},
|
||||||
indexMetadatas: [],
|
fieldIdByName: {},
|
||||||
isSystem: false,
|
isSystem: false,
|
||||||
isCustom: false,
|
isCustom: false,
|
||||||
isActive: true,
|
isActive: true,
|
||||||
isRemote: false,
|
isRemote: false,
|
||||||
isAuditLogged: true,
|
isAuditLogged: true,
|
||||||
isSearchable: true,
|
isSearchable: true,
|
||||||
|
indexMetadatas: [],
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('objectRecordChangedValues', () => {
|
describe('objectRecordChangedValues', () => {
|
||||||
@ -38,7 +39,6 @@ describe('objectRecordChangedValues', () => {
|
|||||||
const result = objectRecordChangedValues(
|
const result = objectRecordChangedValues(
|
||||||
oldRecord,
|
oldRecord,
|
||||||
newRecord,
|
newRecord,
|
||||||
['name'],
|
|
||||||
mockObjectMetadata,
|
mockObjectMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -60,7 +60,6 @@ describe('objectRecordChangedValues', () => {
|
|||||||
const result = objectRecordChangedValues(
|
const result = objectRecordChangedValues(
|
||||||
oldRecord,
|
oldRecord,
|
||||||
newRecord,
|
newRecord,
|
||||||
[],
|
|
||||||
mockObjectMetadata,
|
mockObjectMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -82,7 +81,6 @@ describe('objectRecordChangedValues', () => {
|
|||||||
const result = objectRecordChangedValues(
|
const result = objectRecordChangedValues(
|
||||||
oldRecord,
|
oldRecord,
|
||||||
newRecord,
|
newRecord,
|
||||||
['name', 'value'],
|
|
||||||
mockObjectMetadata,
|
mockObjectMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -112,7 +110,6 @@ describe('objectRecordChangedValues', () => {
|
|||||||
const result = objectRecordChangedValues(
|
const result = objectRecordChangedValues(
|
||||||
oldRecord,
|
oldRecord,
|
||||||
newRecord,
|
newRecord,
|
||||||
['name', 'config', 'status'],
|
|
||||||
mockObjectMetadata,
|
mockObjectMetadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -2,23 +2,25 @@ import deepEqual from 'deep-equal';
|
|||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
|
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
|
|
||||||
export const objectRecordChangedValues = (
|
export const objectRecordChangedValues = (
|
||||||
oldRecord: Partial<ObjectRecord>,
|
oldRecord: Partial<ObjectRecord>,
|
||||||
newRecord: Partial<ObjectRecord>,
|
newRecord: Partial<ObjectRecord>,
|
||||||
updatedKeys: string[] | undefined,
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
||||||
objectMetadataItem: ObjectMetadataInterface,
|
|
||||||
) => {
|
) => {
|
||||||
return Object.keys(newRecord).reduce(
|
return Object.keys(newRecord).reduce(
|
||||||
(acc, key) => {
|
(acc, key) => {
|
||||||
const field = objectMetadataItem.fields.find((f) => f.name === key);
|
const field =
|
||||||
|
objectMetadataItem.fieldsById[objectMetadataItem.fieldIdByName[key]];
|
||||||
|
|
||||||
const oldRecordValue = oldRecord[key];
|
const oldRecordValue = oldRecord[key];
|
||||||
const newRecordValue = newRecord[key];
|
const newRecordValue = newRecord[key];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
key === 'updatedAt' ||
|
key === 'updatedAt' ||
|
||||||
!updatedKeys?.includes(key) ||
|
key === 'searchVector' ||
|
||||||
field?.type === FieldMetadataType.RELATION ||
|
field?.type === FieldMetadataType.RELATION ||
|
||||||
deepEqual(oldRecordValue, newRecordValue)
|
deepEqual(oldRecordValue, newRecordValue)
|
||||||
) {
|
) {
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { DataSource, Repository } from 'typeorm';
|
|||||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||||
|
|
||||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
import { ApprovedAccessDomain } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||||
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
|
||||||
@ -28,17 +27,14 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
import { PermissionsException } from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
describe('UserWorkspaceService', () => {
|
describe('UserWorkspaceService', () => {
|
||||||
let service: UserWorkspaceService;
|
let service: UserWorkspaceService;
|
||||||
let userWorkspaceRepository: Repository<UserWorkspace>;
|
let userWorkspaceRepository: Repository<UserWorkspace>;
|
||||||
let userRepository: Repository<User>;
|
let userRepository: Repository<User>;
|
||||||
let objectMetadataRepository: Repository<ObjectMetadataEntity>;
|
|
||||||
let typeORMService: TypeORMService;
|
let typeORMService: TypeORMService;
|
||||||
let workspaceInvitationService: WorkspaceInvitationService;
|
let workspaceInvitationService: WorkspaceInvitationService;
|
||||||
let workspaceEventEmitter: WorkspaceEventEmitter;
|
|
||||||
let approvedAccessDomainService: ApprovedAccessDomainService;
|
let approvedAccessDomainService: ApprovedAccessDomainService;
|
||||||
let twentyORMGlobalManager: TwentyORMGlobalManager;
|
let twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||||
let userRoleService: UserRoleService;
|
let userRoleService: UserRoleService;
|
||||||
@ -92,13 +88,6 @@ describe('UserWorkspaceService', () => {
|
|||||||
findInvitationsByEmail: jest.fn(),
|
findInvitationsByEmail: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emitCustomBatchEvent: jest.fn(),
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: DomainManagerService,
|
provide: DomainManagerService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -155,16 +144,10 @@ describe('UserWorkspaceService', () => {
|
|||||||
getRepositoryToken(UserWorkspace, 'core'),
|
getRepositoryToken(UserWorkspace, 'core'),
|
||||||
);
|
);
|
||||||
userRepository = module.get(getRepositoryToken(User, 'core'));
|
userRepository = module.get(getRepositoryToken(User, 'core'));
|
||||||
objectMetadataRepository = module.get(
|
|
||||||
getRepositoryToken(ObjectMetadataEntity, 'core'),
|
|
||||||
);
|
|
||||||
typeORMService = module.get<TypeORMService>(TypeORMService);
|
typeORMService = module.get<TypeORMService>(TypeORMService);
|
||||||
workspaceInvitationService = module.get<WorkspaceInvitationService>(
|
workspaceInvitationService = module.get<WorkspaceInvitationService>(
|
||||||
WorkspaceInvitationService,
|
WorkspaceInvitationService,
|
||||||
);
|
);
|
||||||
workspaceEventEmitter = module.get<WorkspaceEventEmitter>(
|
|
||||||
WorkspaceEventEmitter,
|
|
||||||
);
|
|
||||||
approvedAccessDomainService = module.get<ApprovedAccessDomainService>(
|
approvedAccessDomainService = module.get<ApprovedAccessDomainService>(
|
||||||
ApprovedAccessDomainService,
|
ApprovedAccessDomainService,
|
||||||
);
|
);
|
||||||
@ -329,9 +312,6 @@ describe('UserWorkspaceService', () => {
|
|||||||
userEmail: 'test@example.com',
|
userEmail: 'test@example.com',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const objectMetadata = {
|
|
||||||
nameSingular: 'workspaceMember',
|
|
||||||
} as ObjectMetadataEntity;
|
|
||||||
const workspaceMemberRepository = {
|
const workspaceMemberRepository = {
|
||||||
insert: jest.fn(),
|
insert: jest.fn(),
|
||||||
find: jest.fn().mockResolvedValue(workspaceMember),
|
find: jest.fn().mockResolvedValue(workspaceMember),
|
||||||
@ -344,13 +324,6 @@ describe('UserWorkspaceService', () => {
|
|||||||
.spyOn(mainDataSource, 'query')
|
.spyOn(mainDataSource, 'query')
|
||||||
.mockResolvedValueOnce(undefined)
|
.mockResolvedValueOnce(undefined)
|
||||||
.mockResolvedValueOnce(workspaceMember);
|
.mockResolvedValueOnce(workspaceMember);
|
||||||
jest
|
|
||||||
.spyOn(objectMetadataRepository, 'findOneOrFail')
|
|
||||||
.mockResolvedValue(objectMetadata);
|
|
||||||
jest
|
|
||||||
.spyOn(workspaceEventEmitter, 'emitDatabaseBatchEvent')
|
|
||||||
.mockImplementation();
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
.spyOn(twentyORMGlobalManager, 'getRepositoryForWorkspace')
|
||||||
.mockResolvedValue(workspaceMemberRepository as any);
|
.mockResolvedValue(workspaceMemberRepository as any);
|
||||||
@ -372,28 +345,6 @@ describe('UserWorkspaceService', () => {
|
|||||||
locale: 'en',
|
locale: 'en',
|
||||||
avatarUrl: 'userWorkspace-avatar-url',
|
avatarUrl: 'userWorkspace-avatar-url',
|
||||||
});
|
});
|
||||||
expect(objectMetadataRepository.findOneOrFail).toHaveBeenCalledWith({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workspaceMember',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(workspaceEventEmitter.emitDatabaseBatchEvent).toHaveBeenCalledWith(
|
|
||||||
{
|
|
||||||
objectMetadataNameSingular: 'workspaceMember',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workspaceMember[0].id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workspaceMember[0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { IsNull, Not, Repository } from 'typeorm';
|
|||||||
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
|
||||||
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
import { FileFolder } from 'src/engine/core-modules/file/interfaces/file-folder.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
import { AppToken } from 'src/engine/core-modules/app-token/app-token.entity';
|
||||||
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
import { ApprovedAccessDomainService } from 'src/engine/core-modules/approved-access-domain/services/approved-access-domain.service';
|
||||||
import {
|
import {
|
||||||
@ -27,7 +26,6 @@ import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-in
|
|||||||
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -35,7 +33,6 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { assert } from 'src/utils/assert';
|
import { assert } from 'src/utils/assert';
|
||||||
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
||||||
@ -46,10 +43,7 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
private readonly userWorkspaceRepository: Repository<UserWorkspace>,
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
private readonly workspaceInvitationService: WorkspaceInvitationService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly domainManagerService: DomainManagerService,
|
private readonly domainManagerService: DomainManagerService,
|
||||||
private readonly loginTokenService: LoginTokenService,
|
private readonly loginTokenService: LoginTokenService,
|
||||||
private readonly approvedAccessDomainService: ApprovedAccessDomainService,
|
private readonly approvedAccessDomainService: ApprovedAccessDomainService,
|
||||||
@ -124,27 +118,6 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
|
|||||||
workspaceMember?.length === 1,
|
workspaceMember?.length === 1,
|
||||||
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
|
`Error while creating workspace member ${user.email} on workspace ${workspaceId}`,
|
||||||
);
|
);
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workspaceMember',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workspaceMember',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workspaceMember[0].id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workspaceMember[0],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUserToWorkspaceIfUserNotInWorkspace(
|
async addUserToWorkspaceIfUserNotInWorkspace(
|
||||||
|
|||||||
@ -6,17 +6,14 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
|
|||||||
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
import { isWorkspaceActiveOrSuspended } from 'twenty-shared/workspace';
|
||||||
import { IsNull, Not, Repository } from 'typeorm';
|
import { IsNull, Not, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import {
|
import {
|
||||||
AuthException,
|
AuthException,
|
||||||
AuthExceptionCode,
|
AuthExceptionCode,
|
||||||
} from 'src/engine/core-modules/auth/auth.exception';
|
} from 'src/engine/core-modules/auth/auth.exception';
|
||||||
import { UserWorkspaceService } from 'src/engine/core-modules/user-workspace/user-workspace.service';
|
|
||||||
import { User } from 'src/engine/core-modules/user/user.entity';
|
import { User } from 'src/engine/core-modules/user/user.entity';
|
||||||
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
import { userValidator } from 'src/engine/core-modules/user/user.validate';
|
||||||
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
import { WorkspaceService } from 'src/engine/core-modules/workspace/services/workspace.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -24,7 +21,6 @@ import {
|
|||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
// eslint-disable-next-line @nx/workspace-inject-workspace-repository
|
||||||
@ -32,13 +28,9 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(User, 'core')
|
@InjectRepository(User, 'core')
|
||||||
private readonly userRepository: Repository<User>,
|
private readonly userRepository: Repository<User>,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly workspaceService: WorkspaceService,
|
private readonly workspaceService: WorkspaceService,
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly userRoleService: UserRoleService,
|
private readonly userRoleService: UserRoleService,
|
||||||
private readonly userWorkspaceService: UserWorkspaceService,
|
|
||||||
) {
|
) {
|
||||||
super(userRepository);
|
super(userRepository);
|
||||||
}
|
}
|
||||||
@ -157,38 +149,14 @@ export class UserService extends TypeOrmQueryService<User> {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
workspaceMemberRepository,
|
workspaceMemberRepository,
|
||||||
workspaceMembers,
|
workspaceMembers,
|
||||||
workspaceMember,
|
|
||||||
}) => {
|
}) => {
|
||||||
await workspaceMemberRepository.delete({ userId });
|
await workspaceMemberRepository.delete({ userId });
|
||||||
|
|
||||||
const objectMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workspaceMember',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (workspaceMembers.length === 1) {
|
if (workspaceMembers.length === 1) {
|
||||||
await this.workspaceService.deleteWorkspace(workspaceId);
|
await this.workspaceService.deleteWorkspace(workspaceId);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workspaceMember',
|
|
||||||
action: DatabaseEventAction.DELETED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workspaceMember.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: workspaceMember,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { EntityManagerFactory } from 'typeorm/entity-manager/EntityManagerFactor
|
|||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -58,20 +59,29 @@ export class WorkspaceDataSource extends DataSource {
|
|||||||
target: EntityTarget<Entity>,
|
target: EntityTarget<Entity>,
|
||||||
shouldBypassPermissionChecks = false,
|
shouldBypassPermissionChecks = false,
|
||||||
roleId?: string,
|
roleId?: string,
|
||||||
|
authContext?: AuthContext,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
if (shouldBypassPermissionChecks === true) {
|
if (shouldBypassPermissionChecks === true) {
|
||||||
return this.manager.getRepository(target, {
|
return this.manager.getRepository(
|
||||||
shouldBypassPermissionChecks: true,
|
target,
|
||||||
});
|
{
|
||||||
|
shouldBypassPermissionChecks: true,
|
||||||
|
},
|
||||||
|
authContext,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roleId) {
|
if (roleId) {
|
||||||
return this.manager.getRepository(target, {
|
return this.manager.getRepository(
|
||||||
roleId,
|
target,
|
||||||
});
|
{
|
||||||
|
roleId,
|
||||||
|
},
|
||||||
|
authContext,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.manager.getRepository(target);
|
return this.manager.getRepository(target, undefined, authContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
override createEntityManager(
|
override createEntityManager(
|
||||||
|
|||||||
@ -55,8 +55,64 @@ describe('WorkspaceEntityManager', () => {
|
|||||||
mockInternalContext = {
|
mockInternalContext = {
|
||||||
workspaceId: 'test-workspace-id',
|
workspaceId: 'test-workspace-id',
|
||||||
objectMetadataMaps: {
|
objectMetadataMaps: {
|
||||||
idByNameSingular: {},
|
byId: {
|
||||||
|
'test-entity-id': {
|
||||||
|
id: 'test-entity-id',
|
||||||
|
nameSingular: 'test-entity',
|
||||||
|
namePlural: 'test-entities',
|
||||||
|
labelSingular: 'Test Entity',
|
||||||
|
labelPlural: 'Test Entities',
|
||||||
|
workspaceId: 'test-workspace-id',
|
||||||
|
icon: 'test-icon',
|
||||||
|
color: 'test-color',
|
||||||
|
isCustom: false,
|
||||||
|
isRemote: false,
|
||||||
|
isAuditLogged: false,
|
||||||
|
isSearchable: false,
|
||||||
|
isSystem: false,
|
||||||
|
isActive: true,
|
||||||
|
targetTableName: 'test_entity',
|
||||||
|
indexMetadatas: [],
|
||||||
|
fieldsById: {
|
||||||
|
'field-id': {
|
||||||
|
id: 'field-id',
|
||||||
|
type: 'TEXT',
|
||||||
|
name: 'fieldName',
|
||||||
|
label: 'Field Name',
|
||||||
|
objectMetadataId: 'test-entity-id',
|
||||||
|
isNullable: true,
|
||||||
|
isLabelSyncedWithName: false,
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fieldIdByName: { fieldName: 'field-id' },
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
idByNameSingular: {
|
||||||
|
'test-entity': 'test-entity-id',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
featureFlagsMap: {
|
||||||
|
IS_AIRTABLE_INTEGRATION_ENABLED: false,
|
||||||
|
IS_POSTGRESQL_INTEGRATION_ENABLED: false,
|
||||||
|
IS_STRIPE_INTEGRATION_ENABLED: false,
|
||||||
|
IS_UNIQUE_INDEXES_ENABLED: false,
|
||||||
|
IS_JSON_FILTER_ENABLED: false,
|
||||||
|
IS_AI_ENABLED: false,
|
||||||
|
IS_IMAP_SMTP_CALDAV_ENABLED: false,
|
||||||
|
IS_MORPH_RELATION_ENABLED: false,
|
||||||
|
IS_WORKFLOW_FILTERING_ENABLED: false,
|
||||||
|
IS_RELATION_CONNECT_ENABLED: false,
|
||||||
|
IS_WORKSPACE_API_KEY_WEBHOOK_GRAPHQL_ENABLED: false,
|
||||||
|
IS_FIELDS_PERMISSIONS_ENABLED: false,
|
||||||
|
},
|
||||||
|
eventEmitterService: {
|
||||||
|
emitMutationEvent: jest.fn(),
|
||||||
|
emitDatabaseBatchEvent: jest.fn(),
|
||||||
|
emitCustomBatchEvent: jest.fn(),
|
||||||
|
} as any,
|
||||||
} as WorkspaceInternalContext;
|
} as WorkspaceInternalContext;
|
||||||
|
|
||||||
mockDataSource = {
|
mockDataSource = {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
FindManyOptions,
|
FindManyOptions,
|
||||||
FindOneOptions,
|
FindOneOptions,
|
||||||
FindOptionsWhere,
|
FindOptionsWhere,
|
||||||
|
In,
|
||||||
InsertResult,
|
InsertResult,
|
||||||
ObjectId,
|
ObjectId,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
@ -32,6 +33,8 @@ import { InstanceChecker } from 'typeorm/util/InstanceChecker';
|
|||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
@ -51,6 +54,8 @@ import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/wo
|
|||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util';
|
import { computeRelationConnectQueryConfigs } from 'src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util';
|
||||||
import { createSqlWhereTupleInClause } from 'src/engine/twenty-orm/utils/create-sql-where-tuple-in-clause.utils';
|
import { createSqlWhereTupleInClause } from 'src/engine/twenty-orm/utils/create-sql-where-tuple-in-clause.utils';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
import { getRecordToConnectFields } from 'src/engine/twenty-orm/utils/get-record-to-connect-fields.util';
|
import { getRecordToConnectFields } from 'src/engine/twenty-orm/utils/get-record-to-connect-fields.util';
|
||||||
|
|
||||||
@ -85,22 +90,10 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
shouldBypassPermissionChecks?: boolean;
|
shouldBypassPermissionChecks?: boolean;
|
||||||
roleId?: string;
|
roleId?: string;
|
||||||
},
|
},
|
||||||
|
authContext?: AuthContext,
|
||||||
): WorkspaceRepository<Entity> {
|
): WorkspaceRepository<Entity> {
|
||||||
const dataSource = this.connection;
|
const dataSource = this.connection;
|
||||||
|
|
||||||
const repositoryKey = this.getRepositoryKey({
|
|
||||||
target,
|
|
||||||
dataSource,
|
|
||||||
roleId: permissionOptions?.roleId,
|
|
||||||
shouldBypassPermissionChecks:
|
|
||||||
permissionOptions?.shouldBypassPermissionChecks ?? false,
|
|
||||||
});
|
|
||||||
const repoFromMap = this.repositories.get(repositoryKey);
|
|
||||||
|
|
||||||
if (repoFromMap) {
|
|
||||||
return repoFromMap as WorkspaceRepository<Entity>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let objectPermissions = {};
|
let objectPermissions = {};
|
||||||
|
|
||||||
if (permissionOptions?.roleId) {
|
if (permissionOptions?.roleId) {
|
||||||
@ -128,10 +121,9 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
this.queryRunner,
|
this.queryRunner,
|
||||||
objectPermissions,
|
objectPermissions,
|
||||||
permissionOptions?.shouldBypassPermissionChecks,
|
permissionOptions?.shouldBypassPermissionChecks,
|
||||||
|
authContext,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.repositories.set(repositoryKey, newRepository);
|
|
||||||
|
|
||||||
return newRepository;
|
return newRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -360,32 +352,6 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
private getRepositoryKey({
|
|
||||||
target,
|
|
||||||
dataSource,
|
|
||||||
roleId,
|
|
||||||
shouldBypassPermissionChecks,
|
|
||||||
}: {
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
target: EntityTarget<unknown>;
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
validatePermissions<Entity extends ObjectLiteral>(
|
validatePermissions<Entity extends ObjectLiteral>(
|
||||||
target: EntityTarget<Entity> | Entity,
|
target: EntityTarget<Entity> | Entity,
|
||||||
operationType: OperationType,
|
operationType: OperationType,
|
||||||
@ -900,6 +866,13 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
entityLike: DeepPartial<Entity>,
|
entityLike: DeepPartial<Entity>,
|
||||||
permissionOptions?: PermissionOptions,
|
permissionOptions?: PermissionOptions,
|
||||||
): Promise<Entity | undefined> {
|
): Promise<Entity | undefined> {
|
||||||
|
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||||
|
entityClass,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedEntityLike = formatData(entityLike, objectMetadataItem);
|
||||||
|
|
||||||
const managerWithPermissionOptions = Object.assign(
|
const managerWithPermissionOptions = Object.assign(
|
||||||
Object.create(Object.getPrototypeOf(this)),
|
Object.create(Object.getPrototypeOf(this)),
|
||||||
this,
|
this,
|
||||||
@ -915,12 +888,16 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
new PlainObjectToDatabaseEntityTransformer(managerWithPermissionOptions);
|
new PlainObjectToDatabaseEntityTransformer(managerWithPermissionOptions);
|
||||||
const transformedEntity =
|
const transformedEntity =
|
||||||
await plainObjectToDatabaseEntityTransformer.transform(
|
await plainObjectToDatabaseEntityTransformer.transform(
|
||||||
entityLike,
|
formattedEntityLike,
|
||||||
metadata,
|
metadata,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (transformedEntity)
|
if (transformedEntity)
|
||||||
return this.merge(entityClass, transformedEntity, entityLike) as Entity;
|
return this.merge(
|
||||||
|
entityClass,
|
||||||
|
transformedEntity,
|
||||||
|
formattedEntityLike,
|
||||||
|
) as Entity;
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -1075,17 +1052,81 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
const queryRunnerForEntityPersistExecutor =
|
const queryRunnerForEntityPersistExecutor =
|
||||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||||
|
|
||||||
return new EntityPersistExecutor(
|
const isEntityArray = Array.isArray(entity);
|
||||||
|
const entityTarget =
|
||||||
|
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||||
|
|
||||||
|
const entityArray = isEntityArray ? entity : [entity];
|
||||||
|
const entityIds = entityArray.map((e) => (e as { id: string }).id);
|
||||||
|
const beforeUpdate = await this.find(
|
||||||
|
entityTarget,
|
||||||
|
{
|
||||||
|
where: { id: In(entityIds) },
|
||||||
|
},
|
||||||
|
permissionOptions,
|
||||||
|
);
|
||||||
|
|
||||||
|
const beforeUpdateMapById = beforeUpdate.reduce(
|
||||||
|
(acc, e: ObjectLiteral) => {
|
||||||
|
acc[e.id] = e;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, ObjectLiteral>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||||
|
entityTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedEntityOrEntities = formatData(
|
||||||
|
entityArray,
|
||||||
|
objectMetadataItem,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await new EntityPersistExecutor(
|
||||||
this.connection,
|
this.connection,
|
||||||
queryRunnerForEntityPersistExecutor,
|
queryRunnerForEntityPersistExecutor,
|
||||||
'save',
|
'save',
|
||||||
target,
|
target,
|
||||||
entity as ObjectLiteral,
|
formattedEntityOrEntities as ObjectLiteral[],
|
||||||
options as SaveOptions | (SaveOptions & { reload: false }),
|
options as SaveOptions | (SaveOptions & { reload: false }),
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.then(() => entity as Entity)
|
.then(() => formattedEntityOrEntities as Entity[])
|
||||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||||
|
|
||||||
|
const resultArray = Array.isArray(result) ? result : [result];
|
||||||
|
|
||||||
|
const formattedResult = formatResult<Entity[]>(
|
||||||
|
resultArray,
|
||||||
|
objectMetadataItem,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const entity of formattedResult) {
|
||||||
|
const isUpdate = beforeUpdateMapById[entity.id];
|
||||||
|
|
||||||
|
if (isUpdate) {
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.UPDATED,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: [entity],
|
||||||
|
beforeEntities: beforeUpdateMapById[entity.id],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.CREATED,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: [entity],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return isEntityArray ? formattedResult : formattedResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
override remove<Entity>(
|
override remove<Entity>(
|
||||||
@ -1145,23 +1186,49 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
? maybeOptionsOrMaybePermissionOptions
|
? maybeOptionsOrMaybePermissionOptions
|
||||||
: entityOrMaybeOptions;
|
: entityOrMaybeOptions;
|
||||||
|
|
||||||
if (Array.isArray(entity) && entity.length === 0)
|
const isEntityArray = Array.isArray(entity);
|
||||||
return Promise.resolve(entity);
|
|
||||||
|
if (isEntityArray && entity.length === 0) return Promise.resolve(entity);
|
||||||
|
|
||||||
const queryRunnerForEntityPersistExecutor =
|
const queryRunnerForEntityPersistExecutor =
|
||||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||||
|
|
||||||
return new EntityPersistExecutor(
|
const entityTarget =
|
||||||
|
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||||
|
|
||||||
|
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||||
|
entityTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||||
|
|
||||||
|
const result = new EntityPersistExecutor(
|
||||||
this.connection,
|
this.connection,
|
||||||
queryRunnerForEntityPersistExecutor,
|
queryRunnerForEntityPersistExecutor,
|
||||||
'remove',
|
'remove',
|
||||||
target as string | undefined,
|
target as string | undefined,
|
||||||
entity as ObjectLiteral,
|
formattedEntity as ObjectLiteral,
|
||||||
options as RemoveOptions,
|
options as RemoveOptions,
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.then(() => entity as Entity | Entity[])
|
.then(() => formattedEntity as Entity | Entity[])
|
||||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||||
|
|
||||||
|
const formattedResult = formatResult<Entity[]>(
|
||||||
|
result,
|
||||||
|
objectMetadataItem,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.DESTROYED,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
return isEntityArray ? formattedResult : formattedResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
override softRemove<Entity extends ObjectLiteral>(
|
override softRemove<Entity extends ObjectLiteral>(
|
||||||
@ -1237,17 +1304,43 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
const queryRunnerForEntityPersistExecutor =
|
const queryRunnerForEntityPersistExecutor =
|
||||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||||
|
|
||||||
return new EntityPersistExecutor(
|
const isEntityArray = Array.isArray(entity);
|
||||||
|
const entityTarget =
|
||||||
|
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||||
|
|
||||||
|
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||||
|
entityTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||||
|
|
||||||
|
const result = new EntityPersistExecutor(
|
||||||
this.connection,
|
this.connection,
|
||||||
queryRunnerForEntityPersistExecutor,
|
queryRunnerForEntityPersistExecutor,
|
||||||
'soft-remove',
|
'soft-remove',
|
||||||
target,
|
target,
|
||||||
entity as ObjectLiteral,
|
formattedEntity as ObjectLiteral,
|
||||||
options as SaveOptions,
|
options as SaveOptions,
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.then(() => entity as Entity)
|
.then(() => formattedEntity as Entity)
|
||||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||||
|
|
||||||
|
const formattedResult = formatResult<Entity[]>(
|
||||||
|
result,
|
||||||
|
objectMetadataItem,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.DELETED,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
return isEntityArray ? formattedResult : formattedResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
override recover<Entity>(
|
override recover<Entity>(
|
||||||
@ -1313,23 +1406,49 @@ export class WorkspaceEntityManager extends EntityManager {
|
|||||||
: entityOrEntitiesOrMaybeOptions;
|
: entityOrEntitiesOrMaybeOptions;
|
||||||
|
|
||||||
if (InstanceChecker.isEntitySchema(target)) target = target.options.name;
|
if (InstanceChecker.isEntitySchema(target)) target = target.options.name;
|
||||||
if (Array.isArray(entity) && entity.length === 0)
|
const isEntityArray = Array.isArray(entity);
|
||||||
return Promise.resolve(entity);
|
|
||||||
|
if (isEntityArray && entity.length === 0) return Promise.resolve(entity);
|
||||||
|
|
||||||
const queryRunnerForEntityPersistExecutor =
|
const queryRunnerForEntityPersistExecutor =
|
||||||
this.connection.createQueryRunnerForEntityPersistExecutor();
|
this.connection.createQueryRunnerForEntityPersistExecutor();
|
||||||
|
|
||||||
return new EntityPersistExecutor(
|
const entityTarget =
|
||||||
|
target ?? (isEntityArray ? entity[0]?.constructor : entity.constructor);
|
||||||
|
|
||||||
|
const objectMetadataItem = getObjectMetadataFromEntityTarget(
|
||||||
|
entityTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedEntity = formatData(entity, objectMetadataItem);
|
||||||
|
|
||||||
|
const result = new EntityPersistExecutor(
|
||||||
this.connection,
|
this.connection,
|
||||||
queryRunnerForEntityPersistExecutor,
|
queryRunnerForEntityPersistExecutor,
|
||||||
'recover',
|
'recover',
|
||||||
target,
|
target,
|
||||||
entity as ObjectLiteral,
|
formattedEntity as ObjectLiteral,
|
||||||
options as SaveOptions,
|
options as SaveOptions,
|
||||||
)
|
)
|
||||||
.execute()
|
.execute()
|
||||||
.then(() => entity as Entity)
|
.then(() => formattedEntity as Entity)
|
||||||
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
.finally(() => queryRunnerForEntityPersistExecutor.release());
|
||||||
|
|
||||||
|
const formattedResult = formatResult<Entity[]>(
|
||||||
|
result,
|
||||||
|
objectMetadataItem,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.RESTORED,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedResult,
|
||||||
|
});
|
||||||
|
|
||||||
|
return isEntityArray ? formattedResult : formattedResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forbidden methods
|
// Forbidden methods
|
||||||
|
|||||||
@ -21,4 +21,6 @@ export enum TwentyORMExceptionCode {
|
|||||||
CONNECT_RECORD_NOT_FOUND = 'CONNECT_RECORD_NOT_FOUND',
|
CONNECT_RECORD_NOT_FOUND = 'CONNECT_RECORD_NOT_FOUND',
|
||||||
CONNECT_NOT_ALLOWED = 'CONNECT_NOT_ALLOWED',
|
CONNECT_NOT_ALLOWED = 'CONNECT_NOT_ALLOWED',
|
||||||
CONNECT_UNIQUE_CONSTRAINT_ERROR = 'CONNECT_UNIQUE_CONSTRAINT_ERROR',
|
CONNECT_UNIQUE_CONSTRAINT_ERROR = 'CONNECT_UNIQUE_CONSTRAINT_ERROR',
|
||||||
|
MISSING_MAIN_ALIAS_TARGET = 'MISSING_MAIN_ALIAS_TARGET',
|
||||||
|
METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED',
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import { PromiseMemoizer } from 'src/engine/twenty-orm/storage/promise-memoizer.
|
|||||||
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
import { CacheKey } from 'src/engine/twenty-orm/storage/types/cache-key.type';
|
||||||
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
import { getFromCacheWithRecompute } from 'src/engine/utils/get-data-from-cache-with-recompute.util';
|
||||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||||
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
|
|
||||||
type CacheResult<T, U> = {
|
type CacheResult<T, U> = {
|
||||||
version: T;
|
version: T;
|
||||||
@ -51,6 +52,7 @@ export class WorkspaceDatasourceFactory {
|
|||||||
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
|
private readonly workspaceFeatureFlagsMapCacheService: WorkspaceFeatureFlagsMapCacheService,
|
||||||
@InjectRepository(Workspace, 'core')
|
@InjectRepository(Workspace, 'core')
|
||||||
private readonly workspaceRepository: Repository<Workspace>,
|
private readonly workspaceRepository: Repository<Workspace>,
|
||||||
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async conditionalDestroyDataSource(
|
private async conditionalDestroyDataSource(
|
||||||
@ -192,6 +194,7 @@ export class WorkspaceDatasourceFactory {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
objectMetadataMaps: cachedObjectMetadataMaps,
|
objectMetadataMaps: cachedObjectMetadataMaps,
|
||||||
featureFlagsMap: cachedFeatureFlagMap,
|
featureFlagsMap: cachedFeatureFlagMap,
|
||||||
|
eventEmitterService: this.workspaceEventEmitter,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
url:
|
url:
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||||
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
|
|
||||||
export interface WorkspaceInternalContext {
|
export interface WorkspaceInternalContext {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
objectMetadataMaps: ObjectMetadataMaps;
|
objectMetadataMaps: ObjectMetadataMaps;
|
||||||
featureFlagsMap: Record<FeatureFlagKey, boolean>;
|
featureFlagsMap: Record<FeatureFlagKey, boolean>;
|
||||||
|
eventEmitterService: WorkspaceEventEmitter;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -97,24 +97,20 @@ describe('WorkspaceRepository', () => {
|
|||||||
id: 'test-metadata-id',
|
id: 'test-metadata-id',
|
||||||
nameSingular: 'test-entity',
|
nameSingular: 'test-entity',
|
||||||
namePlural: 'test-entities',
|
namePlural: 'test-entities',
|
||||||
fields: [],
|
fieldIdByName: {
|
||||||
|
id: 'test-field-id',
|
||||||
|
},
|
||||||
|
fieldIdByJoinColumnName: {},
|
||||||
|
fieldsById: {
|
||||||
|
'test-field-id': {
|
||||||
|
id: 'test-field-id',
|
||||||
|
name: 'id',
|
||||||
|
type: 'string',
|
||||||
|
isNullable: false,
|
||||||
|
isUnique: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.spyOn(repository as any, 'formatData').mockImplementation((data) => {
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return data.map((item) => Object.assign({}, item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign({}, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.spyOn(repository as any, 'formatResult').mockImplementation((data) => {
|
|
||||||
if (Array.isArray(data)) {
|
|
||||||
return data.map((item) => Object.assign({}, item));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign({}, data);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Find Methods', () => {
|
describe('Find Methods', () => {
|
||||||
@ -239,7 +235,7 @@ describe('WorkspaceRepository', () => {
|
|||||||
|
|
||||||
it('should delegate to workspaceEntityManager delete', async () => {
|
it('should delegate to workspaceEntityManager delete', async () => {
|
||||||
const criteria: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
const criteria: FindOptionsWhere<ObjectLiteral> = { id: 'test-id' };
|
||||||
const expectedResult = { affected: 1, raw: [] };
|
const expectedResult = { affected: 1, raw: [], generatedMaps: [] };
|
||||||
|
|
||||||
mockEntityManager.delete.mockResolvedValue(expectedResult);
|
mockEntityManager.delete.mockResolvedValue(expectedResult);
|
||||||
|
|
||||||
@ -2,6 +2,7 @@ import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
|||||||
import {
|
import {
|
||||||
DeleteQueryBuilder,
|
DeleteQueryBuilder,
|
||||||
DeleteResult,
|
DeleteResult,
|
||||||
|
EntityTarget,
|
||||||
InsertQueryBuilder,
|
InsertQueryBuilder,
|
||||||
ObjectLiteral,
|
ObjectLiteral,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
@ -9,10 +10,18 @@ import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity
|
|||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceDeleteQueryBuilder<
|
export class WorkspaceDeleteQueryBuilder<
|
||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
@ -20,16 +29,19 @@ export class WorkspaceDeleteQueryBuilder<
|
|||||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
private shouldBypassPermissionChecks: boolean;
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private internalContext: WorkspaceInternalContext;
|
private internalContext: WorkspaceInternalContext;
|
||||||
|
private authContext?: AuthContext;
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: DeleteQueryBuilder<T>,
|
queryBuilder: DeleteQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
shouldBypassPermissionChecks: boolean,
|
shouldBypassPermissionChecks: boolean,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): this {
|
override clone(): this {
|
||||||
@ -40,10 +52,11 @@ export class WorkspaceDeleteQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
|
|
||||||
override execute(): Promise<DeleteResult> {
|
override async execute(): Promise<DeleteResult> {
|
||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
@ -51,11 +64,54 @@ export class WorkspaceDeleteQueryBuilder<
|
|||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.execute();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T[]>(
|
||||||
|
result.raw,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.DESTROYED,
|
||||||
|
objectMetadataItem: objectMetadata,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedResult,
|
||||||
|
authContext: this.authContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: result.raw,
|
||||||
|
generatedMaps: formattedResult,
|
||||||
|
affected: result.affected,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMainAliasTarget(): EntityTarget<T> {
|
||||||
|
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||||
|
|
||||||
|
if (!mainAliasTarget) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
'Main alias target is missing',
|
||||||
|
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainAliasTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a select builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a select builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(): WorkspaceUpdateQueryBuilder<T>;
|
override update(): WorkspaceUpdateQueryBuilder<T>;
|
||||||
@ -67,18 +123,30 @@ export class WorkspaceDeleteQueryBuilder<
|
|||||||
override update(
|
override update(
|
||||||
_updateSet?: QueryDeepPartialEntity<T>,
|
_updateSet?: QueryDeepPartialEntity<T>,
|
||||||
): WorkspaceUpdateQueryBuilder<T> {
|
): WorkspaceUpdateQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into an update builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into an update builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override insert(): InsertQueryBuilder<T> {
|
override insert(): InsertQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into an insert builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into an insert builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,28 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { InsertQueryBuilder, ObjectLiteral } from 'typeorm';
|
import {
|
||||||
|
EntityTarget,
|
||||||
|
InsertQueryBuilder,
|
||||||
|
InsertResult,
|
||||||
|
ObjectLiteral,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceInsertQueryBuilder<
|
export class WorkspaceInsertQueryBuilder<
|
||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
@ -15,17 +30,20 @@ export class WorkspaceInsertQueryBuilder<
|
|||||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
private shouldBypassPermissionChecks: boolean;
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private internalContext: WorkspaceInternalContext;
|
private internalContext: WorkspaceInternalContext;
|
||||||
|
private authContext?: AuthContext;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: InsertQueryBuilder<T>,
|
queryBuilder: InsertQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
shouldBypassPermissionChecks: boolean,
|
shouldBypassPermissionChecks: boolean,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): this {
|
override clone(): this {
|
||||||
@ -36,11 +54,26 @@ export class WorkspaceInsertQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
override values(
|
||||||
override execute(): Promise<any> {
|
values: QueryDeepPartialEntity<T> | QueryDeepPartialEntity<T>[],
|
||||||
|
): this {
|
||||||
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedValues = formatData(values, objectMetadata);
|
||||||
|
|
||||||
|
return super.values(formattedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async execute(): Promise<InsertResult> {
|
||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
@ -48,26 +81,81 @@ export class WorkspaceInsertQueryBuilder<
|
|||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.execute();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T[]>(
|
||||||
|
result.raw,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.CREATED,
|
||||||
|
objectMetadataItem: objectMetadata,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedResult,
|
||||||
|
authContext: this.authContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: result.raw,
|
||||||
|
generatedMaps: formattedResult,
|
||||||
|
identifiers: result.identifiers,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMainAliasTarget(): EntityTarget<T> {
|
||||||
|
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||||
|
|
||||||
|
if (!mainAliasTarget) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
'Main alias target is missing',
|
||||||
|
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainAliasTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a select builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a select builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(): WorkspaceUpdateQueryBuilder<T> {
|
override update(): WorkspaceUpdateQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into an update builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into an update builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
import { EntityTarget, ObjectLiteral, SelectQueryBuilder } from 'typeorm';
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||||
import { WorkspaceInsertQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-insert-query-builder';
|
import { WorkspaceInsertQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-insert-query-builder';
|
||||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceSelectQueryBuilder<
|
export class WorkspaceSelectQueryBuilder<
|
||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
@ -20,16 +27,19 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
objectRecordsPermissions: ObjectRecordsPermissions;
|
objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
shouldBypassPermissionChecks: boolean;
|
shouldBypassPermissionChecks: boolean;
|
||||||
internalContext: WorkspaceInternalContext;
|
internalContext: WorkspaceInternalContext;
|
||||||
|
authContext?: AuthContext;
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: SelectQueryBuilder<T>,
|
queryBuilder: SelectQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
shouldBypassPermissionChecks: boolean,
|
shouldBypassPermissionChecks: boolean,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFindOptions() {
|
getFindOptions() {
|
||||||
@ -44,19 +54,55 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
|
|
||||||
override execute(): Promise<T[]> {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
override async execute(): Promise<any> {
|
||||||
this.validatePermissions();
|
this.validatePermissions();
|
||||||
|
|
||||||
return super.execute();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.execute();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T[]>(
|
||||||
|
result,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: result,
|
||||||
|
generatedMaps: formattedResult,
|
||||||
|
identifiers: result.identifiers,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override getMany(): Promise<T[]> {
|
override async getMany(): Promise<T[]> {
|
||||||
this.validatePermissions();
|
this.validatePermissions();
|
||||||
|
|
||||||
return super.getMany();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.getMany();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T[]>(
|
||||||
|
result,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return formattedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -73,16 +119,46 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
return super.getRawMany();
|
return super.getRawMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
override getOne(): Promise<T | null> {
|
override async getOne(): Promise<T | null> {
|
||||||
this.validatePermissions();
|
this.validatePermissions();
|
||||||
|
|
||||||
return super.getOne();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.getOne();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T>(
|
||||||
|
result,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return formattedResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
override getOneOrFail(): Promise<T> {
|
override async getOneOrFail(): Promise<T> {
|
||||||
this.validatePermissions();
|
this.validatePermissions();
|
||||||
|
|
||||||
return super.getOneOrFail();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await super.getOneOrFail();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T>(
|
||||||
|
result,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return formattedResult[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
override getCount(): Promise<number> {
|
override getCount(): Promise<number> {
|
||||||
@ -98,10 +174,25 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override getManyAndCount(): Promise<[T[], number]> {
|
override async getManyAndCount(): Promise<[T[], number]> {
|
||||||
this.validatePermissions();
|
this.validatePermissions();
|
||||||
|
|
||||||
return super.getManyAndCount();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [result, count] = await super.getManyAndCount();
|
||||||
|
|
||||||
|
const formattedResult = formatResult<T[]>(
|
||||||
|
result,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
return [formattedResult, count];
|
||||||
}
|
}
|
||||||
|
|
||||||
override insert(): WorkspaceInsertQueryBuilder<T> {
|
override insert(): WorkspaceInsertQueryBuilder<T> {
|
||||||
@ -112,6 +203,7 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +225,7 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +237,7 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,6 +249,7 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,6 +261,7 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,4 +280,17 @@ export class WorkspaceSelectQueryBuilder<
|
|||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getMainAliasTarget(): EntityTarget<T> {
|
||||||
|
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||||
|
|
||||||
|
if (!mainAliasTarget) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
'Main alias target is missing',
|
||||||
|
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainAliasTarget;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,26 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { InsertQueryBuilder, ObjectLiteral, UpdateResult } from 'typeorm';
|
import {
|
||||||
|
EntityTarget,
|
||||||
|
InsertQueryBuilder,
|
||||||
|
ObjectLiteral,
|
||||||
|
UpdateResult,
|
||||||
|
} from 'typeorm';
|
||||||
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder';
|
import { SoftDeleteQueryBuilder } from 'typeorm/query-builder/SoftDeleteQueryBuilder';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
import { WorkspaceUpdateQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-update-query-builder';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceSoftDeleteQueryBuilder<
|
export class WorkspaceSoftDeleteQueryBuilder<
|
||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
@ -15,17 +28,20 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
|||||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
private shouldBypassPermissionChecks: boolean;
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private internalContext: WorkspaceInternalContext;
|
private internalContext: WorkspaceInternalContext;
|
||||||
|
private authContext?: AuthContext;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: SoftDeleteQueryBuilder<T>,
|
queryBuilder: SoftDeleteQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
shouldBypassPermissionChecks: boolean,
|
shouldBypassPermissionChecks: boolean,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): this {
|
override clone(): this {
|
||||||
@ -36,10 +52,11 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
|
|
||||||
override execute(): Promise<UpdateResult> {
|
override async execute(): Promise<UpdateResult> {
|
||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
@ -47,22 +64,74 @@ export class WorkspaceSoftDeleteQueryBuilder<
|
|||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const after = await super.execute();
|
||||||
|
|
||||||
|
const formattedAfter = formatResult<T[]>(
|
||||||
|
after.raw,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.DELETED,
|
||||||
|
objectMetadataItem: objectMetadata,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedAfter,
|
||||||
|
authContext: this.authContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: after.raw,
|
||||||
|
generatedMaps: formattedAfter,
|
||||||
|
affected: after.affected,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a select builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a select builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override update(): WorkspaceUpdateQueryBuilder<T> {
|
override update(): WorkspaceUpdateQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into an update builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into an update builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override insert(): InsertQueryBuilder<T> {
|
override insert(): InsertQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into an insert builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into an insert builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMainAliasTarget(): EntityTarget<T> {
|
||||||
|
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||||
|
|
||||||
|
if (!mainAliasTarget) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
'Main alias target is missing',
|
||||||
|
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainAliasTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,27 @@
|
|||||||
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
import { ObjectRecordsPermissions } from 'twenty-shared/types';
|
||||||
import { ObjectLiteral, UpdateQueryBuilder, UpdateResult } from 'typeorm';
|
import {
|
||||||
|
EntityTarget,
|
||||||
|
ObjectLiteral,
|
||||||
|
UpdateQueryBuilder,
|
||||||
|
UpdateResult,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import {
|
||||||
|
TwentyORMException,
|
||||||
|
TwentyORMExceptionCode,
|
||||||
|
} from 'src/engine/twenty-orm/exceptions/twenty-orm.exception';
|
||||||
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
import { validateQueryIsPermittedOrThrow } from 'src/engine/twenty-orm/repository/permissions.utils';
|
||||||
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
import { WorkspaceDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-delete-query-builder';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
import { WorkspaceSoftDeleteQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-soft-delete-query-builder';
|
||||||
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
|
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceUpdateQueryBuilder<
|
export class WorkspaceUpdateQueryBuilder<
|
||||||
T extends ObjectLiteral,
|
T extends ObjectLiteral,
|
||||||
@ -14,16 +29,19 @@ export class WorkspaceUpdateQueryBuilder<
|
|||||||
private objectRecordsPermissions: ObjectRecordsPermissions;
|
private objectRecordsPermissions: ObjectRecordsPermissions;
|
||||||
private shouldBypassPermissionChecks: boolean;
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private internalContext: WorkspaceInternalContext;
|
private internalContext: WorkspaceInternalContext;
|
||||||
|
private authContext?: AuthContext;
|
||||||
constructor(
|
constructor(
|
||||||
queryBuilder: UpdateQueryBuilder<T>,
|
queryBuilder: UpdateQueryBuilder<T>,
|
||||||
objectRecordsPermissions: ObjectRecordsPermissions,
|
objectRecordsPermissions: ObjectRecordsPermissions,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
shouldBypassPermissionChecks: boolean,
|
shouldBypassPermissionChecks: boolean,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(queryBuilder);
|
super(queryBuilder);
|
||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
override clone(): this {
|
override clone(): this {
|
||||||
@ -34,10 +52,11 @@ export class WorkspaceUpdateQueryBuilder<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
) as this;
|
) as this;
|
||||||
}
|
}
|
||||||
|
|
||||||
override execute(): Promise<UpdateResult> {
|
override async execute(): Promise<UpdateResult> {
|
||||||
validateQueryIsPermittedOrThrow(
|
validateQueryIsPermittedOrThrow(
|
||||||
this.expressionMap,
|
this.expressionMap,
|
||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
@ -45,22 +64,108 @@ export class WorkspaceUpdateQueryBuilder<
|
|||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
);
|
);
|
||||||
|
|
||||||
return super.execute();
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const beforeSelectQueryBuilder = new WorkspaceSelectQueryBuilder(
|
||||||
|
this as unknown as WorkspaceSelectQueryBuilder<T>,
|
||||||
|
this.objectRecordsPermissions,
|
||||||
|
this.internalContext,
|
||||||
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
beforeSelectQueryBuilder.expressionMap.wheres = this.expressionMap.wheres;
|
||||||
|
beforeSelectQueryBuilder.expressionMap.aliases = this.expressionMap.aliases;
|
||||||
|
beforeSelectQueryBuilder.setParameters(this.getParameters());
|
||||||
|
|
||||||
|
const before = await beforeSelectQueryBuilder.getMany();
|
||||||
|
|
||||||
|
const formattedBefore = formatResult<T[]>(
|
||||||
|
before,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
const after = await super.execute();
|
||||||
|
|
||||||
|
const formattedAfter = formatResult<T[]>(
|
||||||
|
after.raw,
|
||||||
|
objectMetadata,
|
||||||
|
this.internalContext.objectMetadataMaps,
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.internalContext.eventEmitterService.emitMutationEvent({
|
||||||
|
action: DatabaseEventAction.UPDATED,
|
||||||
|
objectMetadataItem: objectMetadata,
|
||||||
|
workspaceId: this.internalContext.workspaceId,
|
||||||
|
entities: formattedAfter,
|
||||||
|
beforeEntities: formattedBefore,
|
||||||
|
authContext: this.authContext,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
raw: after.raw,
|
||||||
|
generatedMaps: formattedAfter,
|
||||||
|
affected: after.affected,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
override set(_values: QueryDeepPartialEntity<T>): this {
|
||||||
|
const mainAliasTarget = this.getMainAliasTarget();
|
||||||
|
|
||||||
|
const objectMetadata = getObjectMetadataFromEntityTarget(
|
||||||
|
mainAliasTarget,
|
||||||
|
this.internalContext,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedUpdateSet = formatData(_values, objectMetadata);
|
||||||
|
|
||||||
|
return super.set(formattedUpdateSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
override select(): WorkspaceSelectQueryBuilder<T> {
|
override select(): WorkspaceSelectQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a select builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a select builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
override delete(): WorkspaceDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override softDelete(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
override restore(): WorkspaceSoftDeleteQueryBuilder<T> {
|
||||||
throw new Error('This builder cannot morph into a soft delete builder');
|
throw new TwentyORMException(
|
||||||
|
'This builder cannot morph into a soft delete builder',
|
||||||
|
TwentyORMExceptionCode.METHOD_NOT_ALLOWED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getMainAliasTarget(): EntityTarget<T> {
|
||||||
|
const mainAliasTarget = this.expressionMap.mainAlias?.target;
|
||||||
|
|
||||||
|
if (!mainAliasTarget) {
|
||||||
|
throw new TwentyORMException(
|
||||||
|
'Main alias target is missing',
|
||||||
|
TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return mainAliasTarget;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,16 +22,15 @@ import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
|||||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import {
|
import {
|
||||||
PermissionsException,
|
PermissionsException,
|
||||||
PermissionsExceptionCode,
|
PermissionsExceptionCode,
|
||||||
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
} from 'src/engine/metadata-modules/permissions/permissions.exception';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
|
||||||
import { QueryDeepPartialEntityWithRelationConnect } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type';
|
import { QueryDeepPartialEntityWithRelationConnect } from 'src/engine/twenty-orm/entity-manager/types/query-deep-partial-entity-with-relation-connect.type';
|
||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
import { WorkspaceSelectQueryBuilder } from 'src/engine/twenty-orm/repository/workspace-select-query-builder';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
import { getObjectMetadataFromEntityTarget } from 'src/engine/twenty-orm/utils/get-object-metadata-from-entity-target.util';
|
||||||
|
|
||||||
export class WorkspaceRepository<
|
export class WorkspaceRepository<
|
||||||
@ -41,6 +40,7 @@ export class WorkspaceRepository<
|
|||||||
private shouldBypassPermissionChecks: boolean;
|
private shouldBypassPermissionChecks: boolean;
|
||||||
private featureFlagMap: FeatureFlagMap;
|
private featureFlagMap: FeatureFlagMap;
|
||||||
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
private objectRecordsPermissions?: ObjectRecordsPermissions;
|
||||||
|
private authContext?: AuthContext;
|
||||||
declare manager: WorkspaceEntityManager;
|
declare manager: WorkspaceEntityManager;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -51,6 +51,7 @@ export class WorkspaceRepository<
|
|||||||
queryRunner?: QueryRunner,
|
queryRunner?: QueryRunner,
|
||||||
objectRecordsPermissions?: ObjectRecordsPermissions,
|
objectRecordsPermissions?: ObjectRecordsPermissions,
|
||||||
shouldBypassPermissionChecks = false,
|
shouldBypassPermissionChecks = false,
|
||||||
|
authContext?: AuthContext,
|
||||||
) {
|
) {
|
||||||
super(target, manager, queryRunner);
|
super(target, manager, queryRunner);
|
||||||
this.internalContext = internalContext;
|
this.internalContext = internalContext;
|
||||||
@ -58,6 +59,7 @@ export class WorkspaceRepository<
|
|||||||
this.objectRecordsPermissions = objectRecordsPermissions;
|
this.objectRecordsPermissions = objectRecordsPermissions;
|
||||||
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
this.shouldBypassPermissionChecks = shouldBypassPermissionChecks;
|
||||||
this.manager = manager;
|
this.manager = manager;
|
||||||
|
this.authContext = authContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
override createQueryBuilder<U extends T>(
|
override createQueryBuilder<U extends T>(
|
||||||
@ -78,6 +80,7 @@ export class WorkspaceRepository<
|
|||||||
this.objectRecordsPermissions,
|
this.objectRecordsPermissions,
|
||||||
this.internalContext,
|
this.internalContext,
|
||||||
this.shouldBypassPermissionChecks,
|
this.shouldBypassPermissionChecks,
|
||||||
|
this.authContext,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,9 +102,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions,
|
computedOptions,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findBy(
|
override async findBy(
|
||||||
@ -119,9 +121,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findAndCount(
|
override async findAndCount(
|
||||||
@ -139,9 +140,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions,
|
computedOptions,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findAndCountBy(
|
override async findAndCountBy(
|
||||||
@ -159,9 +159,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findOne(
|
override async findOne(
|
||||||
@ -179,9 +178,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions,
|
computedOptions,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findOneBy(
|
override async findOneBy(
|
||||||
@ -199,9 +197,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findOneOrFail(
|
override async findOneOrFail(
|
||||||
@ -219,9 +216,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions,
|
computedOptions,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async findOneByOrFail(
|
override async findOneByOrFail(
|
||||||
@ -239,9 +235,8 @@ export class WorkspaceRepository<
|
|||||||
computedOptions.where,
|
computedOptions.where,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
const formattedResult = await this.formatResult(result);
|
|
||||||
|
|
||||||
return formattedResult;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -277,7 +272,6 @@ export class WorkspaceRepository<
|
|||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
|
||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
@ -286,25 +280,23 @@ export class WorkspaceRepository<
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(entityOrEntities)) {
|
||||||
result = await manager.save(
|
result = await manager.save(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.save(
|
result = await manager.save(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result);
|
return result;
|
||||||
|
|
||||||
return formattedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -328,21 +320,18 @@ export class WorkspaceRepository<
|
|||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<T | T[]> {
|
): Promise<T | T[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
};
|
};
|
||||||
const result = await manager.remove(
|
const result = await manager.remove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result);
|
return result;
|
||||||
|
|
||||||
return formattedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async delete(
|
override async delete(
|
||||||
@ -402,7 +391,6 @@ export class WorkspaceRepository<
|
|||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
@ -410,25 +398,23 @@ export class WorkspaceRepository<
|
|||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(entityOrEntities)) {
|
||||||
result = await manager.softRemove(
|
result = await manager.softRemove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.softRemove(
|
result = await manager.softRemove(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result);
|
return result;
|
||||||
|
|
||||||
return formattedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async softDelete(
|
override async softDelete(
|
||||||
@ -491,7 +477,6 @@ export class WorkspaceRepository<
|
|||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<U | U[]> {
|
): Promise<U | U[]> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
@ -499,25 +484,23 @@ export class WorkspaceRepository<
|
|||||||
let result: U | U[];
|
let result: U | U[];
|
||||||
|
|
||||||
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
// Needed because save method has multiple signature, otherwise we will need to do a type assertion
|
||||||
if (Array.isArray(formattedEntityOrEntities)) {
|
if (Array.isArray(entityOrEntities)) {
|
||||||
result = await manager.recover(
|
result = await manager.recover(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
result = await manager.recover(
|
result = await manager.recover(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
options,
|
options,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result);
|
return result;
|
||||||
|
|
||||||
return formattedResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async restore(
|
override async restore(
|
||||||
@ -558,23 +541,12 @@ export class WorkspaceRepository<
|
|||||||
): Promise<InsertResult> {
|
): Promise<InsertResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
|
|
||||||
const formattedEntity = await this.formatData(entity);
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
};
|
};
|
||||||
const result = await manager.insert(
|
|
||||||
this.target,
|
|
||||||
formattedEntity,
|
|
||||||
permissionOptions,
|
|
||||||
);
|
|
||||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
|
||||||
|
|
||||||
return {
|
return manager.insert(this.target, entity, permissionOptions);
|
||||||
raw: result.raw,
|
|
||||||
generatedMaps: formattedResult,
|
|
||||||
identifiers: result.identifiers,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -620,8 +592,6 @@ export class WorkspaceRepository<
|
|||||||
): Promise<InsertResult> {
|
): Promise<InsertResult> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
|
|
||||||
const formattedEntityOrEntities = await this.formatData(entityOrEntities);
|
|
||||||
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
@ -629,16 +599,14 @@ export class WorkspaceRepository<
|
|||||||
|
|
||||||
const result = await manager.upsert(
|
const result = await manager.upsert(
|
||||||
this.target,
|
this.target,
|
||||||
formattedEntityOrEntities,
|
entityOrEntities,
|
||||||
conflictPathsOrOptions,
|
conflictPathsOrOptions,
|
||||||
permissionOptions,
|
permissionOptions,
|
||||||
);
|
);
|
||||||
|
|
||||||
const formattedResult = await this.formatResult(result.generatedMaps);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
raw: result.raw,
|
raw: result.raw,
|
||||||
generatedMaps: formattedResult,
|
generatedMaps: result.generatedMaps,
|
||||||
identifiers: result.identifiers,
|
identifiers: result.identifiers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -862,13 +830,12 @@ export class WorkspaceRepository<
|
|||||||
entityManager?: WorkspaceEntityManager,
|
entityManager?: WorkspaceEntityManager,
|
||||||
): Promise<T | undefined> {
|
): Promise<T | undefined> {
|
||||||
const manager = entityManager || this.manager;
|
const manager = entityManager || this.manager;
|
||||||
const formattedEntityLike = await this.formatData(entityLike);
|
|
||||||
const permissionOptions = {
|
const permissionOptions = {
|
||||||
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
shouldBypassPermissionChecks: this.shouldBypassPermissionChecks,
|
||||||
objectRecordsPermissions: this.objectRecordsPermissions,
|
objectRecordsPermissions: this.objectRecordsPermissions,
|
||||||
};
|
};
|
||||||
|
|
||||||
return manager.preload(this.target, formattedEntityLike, permissionOptions);
|
return manager.preload(this.target, entityLike, permissionOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -940,15 +907,4 @@ export class WorkspaceRepository<
|
|||||||
|
|
||||||
return formatData(data, objectMetadata) as T;
|
return formatData(data, objectMetadata) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
async formatResult<T>(
|
|
||||||
data: T,
|
|
||||||
objectMetadata?: ObjectMetadataItemWithFieldMaps,
|
|
||||||
): Promise<T> {
|
|
||||||
objectMetadata ??= await this.getObjectMetadataFromTarget();
|
|
||||||
|
|
||||||
const objectMetadataMaps = this.internalContext.objectMetadataMaps;
|
|
||||||
|
|
||||||
return formatResult(data, objectMetadata, objectMetadataMaps) as T;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { EntitySchema, EntityTarget, ObjectLiteral } from 'typeorm';
|
|||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import {
|
import {
|
||||||
TwentyORMException,
|
TwentyORMException,
|
||||||
@ -12,7 +13,7 @@ import { WorkspaceEntitiesStorage } from 'src/engine/twenty-orm/storage/workspac
|
|||||||
export const getObjectMetadataFromEntityTarget = <T extends ObjectLiteral>(
|
export const getObjectMetadataFromEntityTarget = <T extends ObjectLiteral>(
|
||||||
entityTarget: EntityTarget<T>,
|
entityTarget: EntityTarget<T>,
|
||||||
internalContext: WorkspaceInternalContext,
|
internalContext: WorkspaceInternalContext,
|
||||||
) => {
|
): ObjectMetadataItemWithFieldMaps => {
|
||||||
const objectMetadataName =
|
const objectMetadataName =
|
||||||
typeof entityTarget === 'string'
|
typeof entityTarget === 'string'
|
||||||
? entityTarget
|
? entityTarget
|
||||||
|
|||||||
@ -1,12 +1,18 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||||
|
|
||||||
|
import { ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
import { ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
||||||
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
import { ObjectRecordDeleteEvent } from 'src/engine/core-modules/event-emitter/types/object-record-delete.event';
|
||||||
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
|
import { ObjectRecordDestroyEvent } from 'src/engine/core-modules/event-emitter/types/object-record-destroy.event';
|
||||||
|
import { ObjectRecordDiff } from 'src/engine/core-modules/event-emitter/types/object-record-diff';
|
||||||
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
|
import { ObjectRecordRestoreEvent } from 'src/engine/core-modules/event-emitter/types/object-record-restore.event';
|
||||||
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
|
import { ObjectRecordUpdateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-update.event';
|
||||||
|
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
||||||
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type';
|
import { CustomEventName } from 'src/engine/workspace-event-emitter/types/custom-event-name.type';
|
||||||
|
|
||||||
type ActionEventMap<T> = {
|
type ActionEventMap<T> = {
|
||||||
@ -21,6 +27,105 @@ type ActionEventMap<T> = {
|
|||||||
export class WorkspaceEventEmitter {
|
export class WorkspaceEventEmitter {
|
||||||
constructor(private readonly eventEmitter: EventEmitter2) {}
|
constructor(private readonly eventEmitter: EventEmitter2) {}
|
||||||
|
|
||||||
|
async emitMutationEvent<T extends ObjectLiteral>({
|
||||||
|
action,
|
||||||
|
objectMetadataItem,
|
||||||
|
workspaceId,
|
||||||
|
authContext,
|
||||||
|
entities,
|
||||||
|
beforeEntities,
|
||||||
|
}: {
|
||||||
|
action: DatabaseEventAction;
|
||||||
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
|
workspaceId: string;
|
||||||
|
authContext?: AuthContext;
|
||||||
|
entities: T | T[];
|
||||||
|
beforeEntities?: T | T[];
|
||||||
|
}) {
|
||||||
|
const objectMetadataNameSingular = objectMetadataItem.nameSingular;
|
||||||
|
const fields = Object.values(objectMetadataItem.fieldsById ?? {});
|
||||||
|
const entityArray = Array.isArray(entities) ? entities : [entities];
|
||||||
|
let events: (
|
||||||
|
| ObjectRecordCreateEvent<T>
|
||||||
|
| ObjectRecordUpdateEvent<T>
|
||||||
|
| ObjectRecordDeleteEvent<T>
|
||||||
|
)[] = [];
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case DatabaseEventAction.CREATED:
|
||||||
|
events = entityArray.map((after) => {
|
||||||
|
const event = new ObjectRecordCreateEvent<T>();
|
||||||
|
|
||||||
|
event.userId = authContext?.user?.id;
|
||||||
|
event.recordId = after.id;
|
||||||
|
event.objectMetadata = { ...objectMetadataItem, fields };
|
||||||
|
event.properties = { after };
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case DatabaseEventAction.UPDATED:
|
||||||
|
events = entityArray.map((after, idx) => {
|
||||||
|
if (!beforeEntities) {
|
||||||
|
throw new Error('beforeEntities is required for UPDATED action');
|
||||||
|
}
|
||||||
|
|
||||||
|
const before = Array.isArray(beforeEntities)
|
||||||
|
? beforeEntities?.[idx]
|
||||||
|
: beforeEntities;
|
||||||
|
|
||||||
|
const diff = objectRecordChangedValues(
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
objectMetadataItem,
|
||||||
|
) as Partial<ObjectRecordDiff<T>>;
|
||||||
|
|
||||||
|
const updatedFields = Object.keys(diff);
|
||||||
|
|
||||||
|
const event = new ObjectRecordUpdateEvent<T>();
|
||||||
|
|
||||||
|
event.userId = authContext?.user?.id;
|
||||||
|
event.recordId = after.id;
|
||||||
|
event.objectMetadata = { ...objectMetadataItem, fields };
|
||||||
|
event.properties = {
|
||||||
|
before,
|
||||||
|
after,
|
||||||
|
updatedFields,
|
||||||
|
diff,
|
||||||
|
};
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case DatabaseEventAction.DELETED:
|
||||||
|
events = entityArray.map((before) => {
|
||||||
|
const event = new ObjectRecordDeleteEvent<T>();
|
||||||
|
|
||||||
|
event.userId = authContext?.user?.id;
|
||||||
|
event.recordId = before.id;
|
||||||
|
event.objectMetadata = { ...objectMetadataItem, fields };
|
||||||
|
event.properties = { before };
|
||||||
|
|
||||||
|
return event;
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!events.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventName = `${objectMetadataNameSingular}.${action}`;
|
||||||
|
|
||||||
|
this.eventEmitter.emit(eventName, {
|
||||||
|
name: eventName,
|
||||||
|
workspaceId,
|
||||||
|
events,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public emitDatabaseBatchEvent<T, A extends keyof ActionEventMap<T>>({
|
public emitDatabaseBatchEvent<T, A extends keyof ActionEventMap<T>>({
|
||||||
objectMetadataNameSingular,
|
objectMetadataNameSingular,
|
||||||
action,
|
action,
|
||||||
|
|||||||
@ -8,11 +8,11 @@ import { DeleteOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver
|
|||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
||||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
||||||
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
|
|
||||||
@WorkspaceQueryHook(`connectedAccount.destroyOne`)
|
@WorkspaceQueryHook(`connectedAccount.destroyOne`)
|
||||||
export class ConnectedAccountDeleteOnePreQueryHook
|
export class ConnectedAccountDeleteOnePreQueryHook
|
||||||
@ -52,6 +52,7 @@ export class ConnectedAccountDeleteOnePreQueryHook
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: handle cascade events for delete
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
||||||
objectMetadataNameSingular: 'messageChannel',
|
objectMetadataNameSingular: 'messageChannel',
|
||||||
action: DatabaseEventAction.DESTROYED,
|
action: DatabaseEventAction.DESTROYED,
|
||||||
|
|||||||
@ -1,18 +1,14 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
import { EmailAccountConnectionParameters } from 'src/engine/core-modules/imap-smtp-caldav-connection/dtos/imap-smtp-caldav-connection.dto';
|
||||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
CalendarEventListFetchJob,
|
CalendarEventListFetchJob,
|
||||||
CalendarEventListFetchJobData,
|
CalendarEventListFetchJobData,
|
||||||
@ -42,9 +38,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
private readonly messageQueueService: MessageQueueService,
|
private readonly messageQueueService: MessageQueueService,
|
||||||
@InjectMessageQueue(MessageQueue.calendarQueue)
|
@InjectMessageQueue(MessageQueue.calendarQueue)
|
||||||
private readonly calendarQueueService: MessageQueueService,
|
private readonly calendarQueueService: MessageQueueService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: WorkspaceRepository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async setupCompleteAccount(input: {
|
async setupCompleteAccount(input: {
|
||||||
@ -97,7 +90,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
await this.upsertConnectedAccount(
|
await this.upsertConnectedAccount(
|
||||||
input,
|
input,
|
||||||
accountId,
|
accountId,
|
||||||
existingAccount,
|
|
||||||
connectedAccountRepository,
|
connectedAccountRepository,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -116,7 +108,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
|
|
||||||
await this.enqueueSyncJobs(
|
await this.enqueueSyncJobs(
|
||||||
input,
|
input,
|
||||||
accountId,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
createdMessageChannel,
|
createdMessageChannel,
|
||||||
createdCalendarChannel,
|
createdCalendarChannel,
|
||||||
@ -131,7 +122,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
connectionParameters: EmailAccountConnectionParameters;
|
connectionParameters: EmailAccountConnectionParameters;
|
||||||
},
|
},
|
||||||
accountId: string,
|
accountId: string,
|
||||||
existingAccount: ConnectedAccountWorkspaceEntity | null,
|
|
||||||
connectedAccountRepository: WorkspaceRepository<ConnectedAccountWorkspaceEntity>,
|
connectedAccountRepository: WorkspaceRepository<ConnectedAccountWorkspaceEntity>,
|
||||||
) {
|
) {
|
||||||
const accountData = {
|
const accountData = {
|
||||||
@ -142,48 +132,7 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
accountOwnerId: input.workspaceMemberId,
|
accountOwnerId: input.workspaceMemberId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedAccount = await connectedAccountRepository.save(accountData, {});
|
await connectedAccountRepository.save(accountData, {});
|
||||||
|
|
||||||
const connectedAccountMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'connectedAccount',
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (existingAccount) {
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'connectedAccount',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: savedAccount.id,
|
|
||||||
objectMetadata: connectedAccountMetadata,
|
|
||||||
properties: {
|
|
||||||
before: existingAccount,
|
|
||||||
after: savedAccount,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'connectedAccount',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: savedAccount.id,
|
|
||||||
objectMetadata: connectedAccountMetadata,
|
|
||||||
properties: {
|
|
||||||
after: savedAccount,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setupMessageChannels(
|
private async setupMessageChannels(
|
||||||
@ -226,29 +175,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageChannelMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'messageChannel',
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'messageChannel',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: newMessageChannel.id,
|
|
||||||
objectMetadata: messageChannelMetadata,
|
|
||||||
properties: {
|
|
||||||
after: newMessageChannel,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return shouldEnableSync ? newMessageChannel : null;
|
return shouldEnableSync ? newMessageChannel : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -289,29 +215,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
|
|
||||||
const calendarChannelMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'calendarChannel',
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'calendarChannel',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: newCalendarChannel.id,
|
|
||||||
objectMetadata: calendarChannelMetadata,
|
|
||||||
properties: {
|
|
||||||
after: newCalendarChannel,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: input.workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return newCalendarChannel;
|
return newCalendarChannel;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -322,7 +225,6 @@ export class ImapSmtpCalDavAPIService {
|
|||||||
input: {
|
input: {
|
||||||
connectionParameters: EmailAccountConnectionParameters;
|
connectionParameters: EmailAccountConnectionParameters;
|
||||||
},
|
},
|
||||||
accountId: string,
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
messageChannel: MessageChannelWorkspaceEntity | null,
|
messageChannel: MessageChannelWorkspaceEntity | null,
|
||||||
calendarChannel: CalendarChannelWorkspaceEntity | null,
|
calendarChannel: CalendarChannelWorkspaceEntity | null,
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { ConnectedAccountProvider } from 'twenty-shared/types';
|
|||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
import {
|
import {
|
||||||
CompanyToCreate,
|
CompanyToCreate,
|
||||||
@ -103,12 +102,6 @@ describe('CreateCompanyService', () => {
|
|||||||
.mockResolvedValue(mockCompanyRepository),
|
.mockResolvedValue(mockCompanyRepository),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
||||||
useValue: {
|
useValue: {
|
||||||
|
|||||||
@ -1,18 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import chunk from 'lodash.chunk';
|
import chunk from 'lodash.chunk';
|
||||||
import compact from 'lodash.compact';
|
import compact from 'lodash.compact';
|
||||||
import { DeepPartial, Repository } from 'typeorm';
|
import { DeepPartial } 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';
|
import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant';
|
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant';
|
||||||
import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service';
|
import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service';
|
||||||
@ -31,9 +26,6 @@ export class CreateCompanyAndContactService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly createContactService: CreateContactService,
|
private readonly createContactService: CreateContactService,
|
||||||
private readonly createCompaniesService: CreateCompanyService,
|
private readonly createCompaniesService: CreateCompanyService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly exceptionHandlerService: ExceptionHandlerService,
|
private readonly exceptionHandlerService: ExceptionHandlerService,
|
||||||
) {}
|
) {}
|
||||||
@ -85,14 +77,10 @@ export class CreateCompanyAndContactService {
|
|||||||
emails: uniqueHandles,
|
emails: uniqueHandles,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawAlreadyCreatedContacts = await queryBuilder
|
const alreadyCreatedContacts = await queryBuilder
|
||||||
.orderBy('person.createdAt', 'ASC')
|
.orderBy('person.createdAt', 'ASC')
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const alreadyCreatedContacts = await personRepository.formatResult(
|
|
||||||
rawAlreadyCreatedContacts,
|
|
||||||
);
|
|
||||||
|
|
||||||
const alreadyCreatedContactEmails: string[] =
|
const alreadyCreatedContactEmails: string[] =
|
||||||
alreadyCreatedContacts?.reduce<string[]>((acc, { emails }) => {
|
alreadyCreatedContacts?.reduce<string[]>((acc, { emails }) => {
|
||||||
const currentContactEmails: string[] = [];
|
const currentContactEmails: string[] = [];
|
||||||
@ -190,19 +178,6 @@ export class CreateCompanyAndContactService {
|
|||||||
CONTACTS_CREATION_BATCH_SIZE,
|
CONTACTS_CREATION_BATCH_SIZE,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Remove this when events are emitted directly inside TwentyORM
|
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
standardId: STANDARD_OBJECT_IDS.person,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new Error('Object metadata not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
// In some jobs the accountOwner is not populated
|
// In some jobs the accountOwner is not populated
|
||||||
if (!connectedAccount.accountOwner) {
|
if (!connectedAccount.accountOwner) {
|
||||||
const workspaceMemberRepository =
|
const workspaceMemberRepository =
|
||||||
@ -228,26 +203,12 @@ export class CreateCompanyAndContactService {
|
|||||||
|
|
||||||
for (const contactsBatch of contactsBatches) {
|
for (const contactsBatch of contactsBatches) {
|
||||||
try {
|
try {
|
||||||
const createdPeople = await this.createCompaniesAndPeople(
|
await this.createCompaniesAndPeople(
|
||||||
connectedAccount,
|
connectedAccount,
|
||||||
contactsBatch,
|
contactsBatch,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
source,
|
source,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'person',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: createdPeople.map((createdPerson) => ({
|
|
||||||
// Fix ' as string': TypeORM typing issue... id is always returned when using save
|
|
||||||
recordId: createdPerson.id as string,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: createdPerson,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.exceptionHandlerService.captureExceptions([error], {
|
this.exceptionHandlerService.captureExceptions([error], {
|
||||||
workspace: {
|
workspace: {
|
||||||
|
|||||||
@ -1,20 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import uniqBy from 'lodash.uniqby';
|
import uniqBy from 'lodash.uniqby';
|
||||||
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
import { TWENTY_COMPANIES_BASE_URL } from 'twenty-shared/constants';
|
||||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||||
import { lowercaseUrlOriginAndRemoveTrailingSlash } from 'twenty-shared/utils';
|
import { lowercaseUrlOriginAndRemoveTrailingSlash } from 'twenty-shared/utils';
|
||||||
import { DeepPartial, ILike, Repository } from 'typeorm';
|
import { DeepPartial, ILike } from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
|
||||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||||
import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util';
|
import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util';
|
||||||
import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util';
|
import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util';
|
||||||
@ -34,12 +29,7 @@ export type CompanyToCreate = {
|
|||||||
export class CreateCompanyService {
|
export class CreateCompanyService {
|
||||||
private readonly httpService: AxiosInstance;
|
private readonly httpService: AxiosInstance;
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly twentyORMGlobalManager: TwentyORMGlobalManager) {
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {
|
|
||||||
this.httpService = axios.create({
|
this.httpService = axios.create({
|
||||||
baseURL: TWENTY_COMPANIES_BASE_URL,
|
baseURL: TWENTY_COMPANIES_BASE_URL,
|
||||||
});
|
});
|
||||||
@ -55,17 +45,6 @@ export class CreateCompanyService {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
standardId: STANDARD_OBJECT_IDS.company,
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new Error('Object metadata not found');
|
|
||||||
}
|
|
||||||
|
|
||||||
const companyRepository =
|
const companyRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -124,19 +103,6 @@ export class CreateCompanyService {
|
|||||||
// Create new companies
|
// Create new companies
|
||||||
const createdCompanies = await companyRepository.save(newCompaniesData);
|
const createdCompanies = await companyRepository.save(newCompaniesData);
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'company',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: createdCompanies.map((createdCompany) => ({
|
|
||||||
recordId: createdCompany.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: createdCompany,
|
|
||||||
},
|
|
||||||
})),
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const createdCompanyIdsMap = this.createCompanyMap(createdCompanies);
|
const createdCompanyIdsMap = this.createCompanyMap(createdCompanies);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -20,18 +20,15 @@ describe('MatchParticipantService', () => {
|
|||||||
find: jest.Mock;
|
find: jest.Mock;
|
||||||
update: jest.Mock;
|
update: jest.Mock;
|
||||||
createQueryBuilder: jest.Mock;
|
createQueryBuilder: jest.Mock;
|
||||||
formatResult: jest.Mock;
|
|
||||||
};
|
};
|
||||||
let mockCalendarEventParticipantRepository: {
|
let mockCalendarEventParticipantRepository: {
|
||||||
find: jest.Mock;
|
find: jest.Mock;
|
||||||
update: jest.Mock;
|
update: jest.Mock;
|
||||||
createQueryBuilder: jest.Mock;
|
createQueryBuilder: jest.Mock;
|
||||||
formatResult: jest.Mock;
|
|
||||||
};
|
};
|
||||||
let mockPersonRepository: {
|
let mockPersonRepository: {
|
||||||
find: jest.Mock;
|
find: jest.Mock;
|
||||||
createQueryBuilder: jest.Mock;
|
createQueryBuilder: jest.Mock;
|
||||||
formatResult: jest.Mock;
|
|
||||||
};
|
};
|
||||||
let mockWorkspaceMemberRepository: {
|
let mockWorkspaceMemberRepository: {
|
||||||
find: jest.Mock;
|
find: jest.Mock;
|
||||||
@ -53,7 +50,6 @@ describe('MatchParticipantService', () => {
|
|||||||
getMany: jest.fn(),
|
getMany: jest.fn(),
|
||||||
withDeleted: jest.fn().mockReturnThis(),
|
withDeleted: jest.fn().mockReturnThis(),
|
||||||
}),
|
}),
|
||||||
formatResult: jest.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mockCalendarEventParticipantRepository = {
|
mockCalendarEventParticipantRepository = {
|
||||||
@ -68,7 +64,6 @@ describe('MatchParticipantService', () => {
|
|||||||
getMany: jest.fn(),
|
getMany: jest.fn(),
|
||||||
withDeleted: jest.fn().mockReturnThis(),
|
withDeleted: jest.fn().mockReturnThis(),
|
||||||
}),
|
}),
|
||||||
formatResult: jest.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mockPersonRepository = {
|
mockPersonRepository = {
|
||||||
@ -82,7 +77,6 @@ describe('MatchParticipantService', () => {
|
|||||||
getMany: jest.fn(),
|
getMany: jest.fn(),
|
||||||
withDeleted: jest.fn().mockReturnThis(),
|
withDeleted: jest.fn().mockReturnThis(),
|
||||||
}),
|
}),
|
||||||
formatResult: jest.fn(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mockWorkspaceMemberRepository = {
|
mockWorkspaceMemberRepository = {
|
||||||
@ -204,7 +198,7 @@ describe('MatchParticipantService', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
mockPersonRepository.formatResult.mockResolvedValue(mockPeople);
|
mockQueryBuilder.getMany.mockResolvedValue(mockPeople);
|
||||||
mockWorkspaceMemberRepository.find.mockResolvedValue(
|
mockWorkspaceMemberRepository.find.mockResolvedValue(
|
||||||
mockWorkspaceMembers,
|
mockWorkspaceMembers,
|
||||||
);
|
);
|
||||||
@ -316,7 +310,18 @@ describe('MatchParticipantService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle participants with no matching people or workspace members', async () => {
|
it('should handle participants with no matching people or workspace members', async () => {
|
||||||
mockPersonRepository.formatResult.mockResolvedValue([]);
|
const mockQueryBuilder = {
|
||||||
|
select: jest.fn().mockReturnThis(),
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
getMany: jest.fn().mockResolvedValue([]),
|
||||||
|
withDeleted: jest.fn().mockReturnThis(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
mockQueryBuilder.getMany.mockResolvedValue([]);
|
||||||
mockWorkspaceMemberRepository.find.mockResolvedValue([]);
|
mockWorkspaceMemberRepository.find.mockResolvedValue([]);
|
||||||
|
|
||||||
await service.matchParticipants({
|
await service.matchParticipants({
|
||||||
@ -545,7 +550,18 @@ describe('MatchParticipantService', () => {
|
|||||||
affected: 1,
|
affected: 1,
|
||||||
});
|
});
|
||||||
mockMessageParticipantRepository.find.mockResolvedValue([]);
|
mockMessageParticipantRepository.find.mockResolvedValue([]);
|
||||||
mockPersonRepository.formatResult.mockResolvedValue([]);
|
const mockQueryBuilder = {
|
||||||
|
select: jest.fn().mockReturnThis(),
|
||||||
|
where: jest.fn().mockReturnThis(),
|
||||||
|
andWhere: jest.fn().mockReturnThis(),
|
||||||
|
orWhere: jest.fn().mockReturnThis(),
|
||||||
|
orderBy: jest.fn().mockReturnThis(),
|
||||||
|
getMany: jest.fn().mockResolvedValue([]),
|
||||||
|
withDeleted: jest.fn().mockReturnThis(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockPersonRepository.createQueryBuilder.mockReturnValue(mockQueryBuilder);
|
||||||
|
mockQueryBuilder.getMany.mockResolvedValue([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('person unmatching', () => {
|
describe('person unmatching', () => {
|
||||||
@ -590,9 +606,7 @@ describe('MatchParticipantService', () => {
|
|||||||
mockPersonRepository.createQueryBuilder.mockReturnValue(
|
mockPersonRepository.createQueryBuilder.mockReturnValue(
|
||||||
mockQueryBuilder,
|
mockQueryBuilder,
|
||||||
);
|
);
|
||||||
mockPersonRepository.formatResult.mockResolvedValue(
|
mockQueryBuilder.getMany.mockResolvedValue(mockAlternativePeople);
|
||||||
mockAlternativePeople,
|
|
||||||
);
|
|
||||||
|
|
||||||
const rematchedParticipants = [
|
const rematchedParticipants = [
|
||||||
{
|
{
|
||||||
@ -655,7 +669,7 @@ describe('MatchParticipantService', () => {
|
|||||||
mockPersonRepository.createQueryBuilder.mockReturnValue(
|
mockPersonRepository.createQueryBuilder.mockReturnValue(
|
||||||
mockQueryBuilder,
|
mockQueryBuilder,
|
||||||
);
|
);
|
||||||
mockPersonRepository.formatResult.mockResolvedValue([]);
|
mockQueryBuilder.getMany.mockResolvedValue([]);
|
||||||
|
|
||||||
await service.unmatchParticipants({
|
await service.unmatchParticipants({
|
||||||
handle: 'test-1@example.com',
|
handle: 'test-1@example.com',
|
||||||
|
|||||||
@ -82,12 +82,10 @@ export class MatchParticipantService<
|
|||||||
emails: uniqueParticipantsHandles,
|
emails: uniqueParticipantsHandles,
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawPeople = await queryBuilder
|
const people = await queryBuilder
|
||||||
.orderBy('person.createdAt', 'ASC')
|
.orderBy('person.createdAt', 'ASC')
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const people = await personRepository.formatResult(rawPeople);
|
|
||||||
|
|
||||||
const workspaceMemberRepository =
|
const workspaceMemberRepository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<WorkspaceMemberWorkspaceEntity>(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@ -192,12 +190,10 @@ export class MatchParticipantService<
|
|||||||
excludePersonIds: [personId],
|
excludePersonIds: [personId],
|
||||||
});
|
});
|
||||||
|
|
||||||
const rawPeople = await queryBuilder
|
const peopleToMatch = await queryBuilder
|
||||||
.orderBy('person.createdAt', 'ASC')
|
.orderBy('person.createdAt', 'ASC')
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
const peopleToMatch = await personRepository.formatResult(rawPeople);
|
|
||||||
|
|
||||||
if (peopleToMatch.length > 0) {
|
if (peopleToMatch.length > 0) {
|
||||||
const bestMatch = findPersonByPrimaryOrAdditionalEmail({
|
const bestMatch = findPersonByPrimaryOrAdditionalEmail({
|
||||||
people: peopleToMatch,
|
people: peopleToMatch,
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
MessageChannelSyncStatus,
|
MessageChannelSyncStatus,
|
||||||
MessageChannelWorkspaceEntity,
|
MessageChannelWorkspaceEntity,
|
||||||
@ -18,7 +15,6 @@ export class ImapHandleErrorService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async handleError(
|
async handleError(
|
||||||
@ -38,42 +34,12 @@ export class ImapHandleErrorService {
|
|||||||
'messageChannel',
|
'messageChannel',
|
||||||
);
|
);
|
||||||
|
|
||||||
const messageChannel = await messageChannelRepository.findOneOrFail({
|
|
||||||
where: { id: messageChannelId },
|
|
||||||
});
|
|
||||||
|
|
||||||
await messageChannelRepository.update(
|
await messageChannelRepository.update(
|
||||||
{ id: messageChannelId },
|
{ id: messageChannelId },
|
||||||
{
|
{
|
||||||
syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN,
|
syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const dataSource =
|
|
||||||
await this.twentyORMGlobalManager.getDataSourceForWorkspace({
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
const messageChannelMetadata = await dataSource
|
|
||||||
.getRepository(ObjectMetadataEntity)
|
|
||||||
.findOneOrFail({
|
|
||||||
where: { nameSingular: 'messageChannel', workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'messageChannel',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: messageChannelId,
|
|
||||||
objectMetadata: messageChannelMetadata,
|
|
||||||
properties: {
|
|
||||||
before: { syncStatus: messageChannel.syncStatus },
|
|
||||||
after: { syncStatus: MessageChannelSyncStatus.FAILED_UNKNOWN },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
} catch (handleErrorError) {
|
} catch (handleErrorError) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`Error handling IMAP error: ${handleErrorError.message}`,
|
`Error handling IMAP error: ${handleErrorError.message}`,
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-q
|
|||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
import { CreateCompanyAndContactJob } from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
||||||
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
import { MessageDirection } from 'src/modules/messaging/common/enums/message-direction.enum';
|
||||||
@ -104,12 +103,6 @@ describe('MessagingSaveMessagesAndEnqueueContactCreationService', () => {
|
|||||||
add: jest.fn().mockResolvedValue(undefined),
|
add: jest.fn().mockResolvedValue(undefined),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emitDatabaseBatchEvent: jest.fn().mockResolvedValue(undefined),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
||||||
useValue: {
|
useValue: {
|
||||||
|
|||||||
@ -1,17 +1,11 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/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';
|
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
import { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import {
|
import {
|
||||||
CreateCompanyAndContactJob,
|
CreateCompanyAndContactJob,
|
||||||
@ -39,9 +33,6 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
|||||||
private readonly messageService: MessagingMessageService,
|
private readonly messageService: MessagingMessageService,
|
||||||
private readonly messageParticipantService: MessagingMessageParticipantService,
|
private readonly messageParticipantService: MessagingMessageParticipantService,
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async saveMessagesAndEnqueueContactCreation(
|
async saveMessagesAndEnqueueContactCreation(
|
||||||
@ -122,27 +113,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const { participantsWithMessageId, createdMessages } =
|
const { participantsWithMessageId } = createdMessagesWithParticipants;
|
||||||
createdMessagesWithParticipants;
|
|
||||||
|
|
||||||
const messageMetadata = await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: { nameSingular: 'message', workspaceId },
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'message',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: createdMessages.map((message) => {
|
|
||||||
return {
|
|
||||||
recordId: message.id ?? '',
|
|
||||||
objectMetadata: messageMetadata,
|
|
||||||
properties: {
|
|
||||||
after: message,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (messageChannel.isContactAutoCreationEnabled) {
|
if (messageChannel.isContactAutoCreationEnabled) {
|
||||||
const contactsToCreate = participantsWithMessageId.filter(
|
const contactsToCreate = participantsWithMessageId.filter(
|
||||||
|
|||||||
@ -1,23 +1,16 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
|
|
||||||
@WorkspaceQueryHook({
|
@WorkspaceQueryHook({
|
||||||
key: `workflow.createMany`,
|
key: `workflow.createMany`,
|
||||||
@ -28,9 +21,6 @@ export class WorkflowCreateManyPostQueryHook
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly recordPositionService: RecordPositionService,
|
private readonly recordPositionService: RecordPositionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -71,28 +61,5 @@ export class WorkflowCreateManyPostQueryHook
|
|||||||
return workflowVersionRepository.save(workflowVersion);
|
return workflowVersionRepository.save(workflowVersion);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflowVersion',
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workflowVersion',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: workflowVersionsToCreate.map((workflowVersionToCreate) => {
|
|
||||||
return {
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: workflowVersionToCreate.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workflowVersionToCreate,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,23 +1,16 @@
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
import { WorkspacePostQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator';
|
||||||
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
import { WorkspaceQueryHookType } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/types/workspace-query-hook.type';
|
||||||
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
||||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
} from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||||
import { workspaceValidator } from 'src/engine/core-modules/workspace/workspace.validate';
|
|
||||||
|
|
||||||
@WorkspaceQueryHook({
|
@WorkspaceQueryHook({
|
||||||
key: `workflow.createOne`,
|
key: `workflow.createOne`,
|
||||||
@ -28,9 +21,6 @@ export class WorkflowCreateOnePostQueryHook
|
|||||||
{
|
{
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly recordPositionService: RecordPositionService,
|
private readonly recordPositionService: RecordPositionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -59,7 +49,7 @@ export class WorkflowCreateOnePostQueryHook
|
|||||||
workspaceId: workspace.id,
|
workspaceId: workspace.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
const workflowVersionToCreate = await workflowVersionRepository.create({
|
const workflowVersionToCreate = workflowVersionRepository.create({
|
||||||
workflowId: workflow.id,
|
workflowId: workflow.id,
|
||||||
status: WorkflowVersionStatus.DRAFT,
|
status: WorkflowVersionStatus.DRAFT,
|
||||||
name: 'v1',
|
name: 'v1',
|
||||||
@ -67,28 +57,5 @@ export class WorkflowCreateOnePostQueryHook
|
|||||||
});
|
});
|
||||||
|
|
||||||
await workflowVersionRepository.save(workflowVersionToCreate);
|
await workflowVersionRepository.save(workflowVersionToCreate);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflowVersion',
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workflowVersion',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
userId: authContext.user?.id,
|
|
||||||
recordId: workflowVersionToCreate.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workflowVersionToCreate,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId: workspace.id,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,9 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStepException,
|
WorkflowVersionStepException,
|
||||||
WorkflowVersionStepExceptionCode,
|
WorkflowVersionStepExceptionCode,
|
||||||
@ -28,9 +23,6 @@ export class WorkflowVersionWorkspaceService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
private readonly workflowVersionStepWorkspaceService: WorkflowVersionStepWorkspaceService,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly recordPositionService: RecordPositionService,
|
private readonly recordPositionService: RecordPositionService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -96,11 +88,6 @@ export class WorkflowVersionWorkspaceService {
|
|||||||
status: WorkflowVersionStatus.DRAFT,
|
status: WorkflowVersionStatus.DRAFT,
|
||||||
position,
|
position,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.emitWorkflowVersionCreationEvent({
|
|
||||||
workflowVersion: draftWorkflowVersion,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertWorkflowVersionIsDraft(draftWorkflowVersion);
|
assertWorkflowVersionIsDraft(draftWorkflowVersion);
|
||||||
@ -125,41 +112,4 @@ export class WorkflowVersionWorkspaceService {
|
|||||||
|
|
||||||
return draftWorkflowVersion.id;
|
return draftWorkflowVersion.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async emitWorkflowVersionCreationEvent({
|
|
||||||
workflowVersion,
|
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
workflowVersion: WorkflowVersionWorkspaceEntity;
|
|
||||||
workspaceId: string;
|
|
||||||
}) {
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflowVersion',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new WorkflowVersionStepException(
|
|
||||||
'Object metadata not found',
|
|
||||||
WorkflowVersionStepExceptionCode.FAILURE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workflowVersion',
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workflowVersion.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workflowVersion,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,14 +6,12 @@ import { Repository } from 'typeorm';
|
|||||||
|
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||||
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
WorkflowStepExecutorException,
|
WorkflowStepExecutorException,
|
||||||
@ -35,7 +33,6 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
|
|||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
@InjectRepository(ObjectMetadataEntity, 'core')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
private readonly recordPositionService: RecordPositionService,
|
private readonly recordPositionService: RecordPositionService,
|
||||||
private readonly recordInputTransformerService: RecordInputTransformerService,
|
private readonly recordInputTransformerService: RecordInputTransformerService,
|
||||||
@ -130,21 +127,6 @@ export class CreateRecordWorkflowAction implements WorkflowAction {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: workflowActionInput.objectName,
|
|
||||||
action: DatabaseEventAction.CREATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: objectRecord.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: objectRecord,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: objectRecord,
|
result: objectRecord,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,17 +1,12 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { isDefined } from 'class-validator';
|
import { isDefined } from 'class-validator';
|
||||||
import { isValidUuid } from 'twenty-shared/utils';
|
import { isValidUuid } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
WorkflowStepExecutorException,
|
WorkflowStepExecutorException,
|
||||||
WorkflowStepExecutorExceptionCode,
|
WorkflowStepExecutorExceptionCode,
|
||||||
@ -30,9 +25,6 @@ import { WorkflowDeleteRecordActionInput } from 'src/modules/workflow/workflow-e
|
|||||||
export class DeleteRecordWorkflowAction implements WorkflowAction {
|
export class DeleteRecordWorkflowAction implements WorkflowAction {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -88,19 +80,6 @@ export class DeleteRecordWorkflowAction implements WorkflowAction {
|
|||||||
{ shouldBypassPermissionChecks: true },
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
nameSingular: workflowActionInput.objectName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new RecordCRUDActionException(
|
|
||||||
'Failed to delete: Object metadata not found',
|
|
||||||
RecordCRUDActionExceptionCode.INVALID_REQUEST,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectRecord = await repository.findOne({
|
const objectRecord = await repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: workflowActionInput.objectRecordId,
|
id: workflowActionInput.objectRecordId,
|
||||||
@ -116,21 +95,6 @@ export class DeleteRecordWorkflowAction implements WorkflowAction {
|
|||||||
|
|
||||||
await repository.softDelete(workflowActionInput.objectRecordId);
|
await repository.softDelete(workflowActionInput.objectRecordId);
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: workflowActionInput.objectName,
|
|
||||||
action: DatabaseEventAction.DELETED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: objectRecord.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: objectRecord,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: objectRecord,
|
result: objectRecord,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,7 +17,6 @@ import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-met
|
|||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
WorkflowStepExecutorException,
|
WorkflowStepExecutorException,
|
||||||
@ -147,15 +146,9 @@ export class FindRecordsWorkflowAction implements WorkflowAction {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
const nonFormattedObjectRecords = await withOrderByQueryBuilder
|
return withOrderByQueryBuilder
|
||||||
.take(workflowActionInput.limit ?? QUERY_MAX_RECORDS)
|
.take(workflowActionInput.limit ?? QUERY_MAX_RECORDS)
|
||||||
.getMany();
|
.getMany();
|
||||||
|
|
||||||
return formatResult<T[]>(
|
|
||||||
nonFormattedObjectRecords,
|
|
||||||
objectMetadataItemWithFieldsMaps,
|
|
||||||
objectMetadataMaps,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getTotalCount(
|
private async getTotalCount(
|
||||||
|
|||||||
@ -1,20 +1,13 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import deepEqual from 'deep-equal';
|
import deepEqual from 'deep-equal';
|
||||||
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
import { isDefined, isValidUuid } from 'twenty-shared/utils';
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/interfaces/workflow-action.interface';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
|
||||||
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
import { RecordInputTransformerService } from 'src/engine/core-modules/record-transformer/services/record-input-transformer.service';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import {
|
import {
|
||||||
WorkflowStepExecutorException,
|
WorkflowStepExecutorException,
|
||||||
@ -35,9 +28,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly recordInputTransformerService: RecordInputTransformerService,
|
private readonly recordInputTransformerService: RecordInputTransformerService,
|
||||||
) {}
|
) {}
|
||||||
@ -95,20 +85,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
|
|||||||
{ shouldBypassPermissionChecks: true },
|
{ shouldBypassPermissionChecks: true },
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
nameSingular: workflowActionInput.objectName,
|
|
||||||
},
|
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new RecordCRUDActionException(
|
|
||||||
'Failed to update: Object metadata not found',
|
|
||||||
RecordCRUDActionExceptionCode.INVALID_REQUEST,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const previousObjectRecord = await repository.findOne({
|
const previousObjectRecord = await repository.findOne({
|
||||||
where: {
|
where: {
|
||||||
id: workflowActionInput.objectRecordId,
|
id: workflowActionInput.objectRecordId,
|
||||||
@ -153,11 +129,6 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
|
|||||||
objectMetadataMapItem: objectMetadataItemWithFieldsMaps,
|
objectMetadataMapItem: objectMetadataItemWithFieldsMaps,
|
||||||
});
|
});
|
||||||
|
|
||||||
const objectRecordFormatted = formatData(
|
|
||||||
transformedObjectRecord,
|
|
||||||
objectMetadataItemWithFieldsMaps,
|
|
||||||
);
|
|
||||||
|
|
||||||
const updatedObjectRecord = {
|
const updatedObjectRecord = {
|
||||||
...previousObjectRecord,
|
...previousObjectRecord,
|
||||||
...objectRecordWithFilteredFields,
|
...objectRecordWithFilteredFields,
|
||||||
@ -165,32 +136,7 @@ export class UpdateRecordWorkflowAction implements WorkflowAction {
|
|||||||
|
|
||||||
if (!deepEqual(updatedObjectRecord, previousObjectRecord)) {
|
if (!deepEqual(updatedObjectRecord, previousObjectRecord)) {
|
||||||
await repository.update(workflowActionInput.objectRecordId, {
|
await repository.update(workflowActionInput.objectRecordId, {
|
||||||
...objectRecordFormatted,
|
...transformedObjectRecord,
|
||||||
});
|
|
||||||
|
|
||||||
const diff = objectRecordChangedValues(
|
|
||||||
previousObjectRecord,
|
|
||||||
updatedObjectRecord,
|
|
||||||
workflowActionInput.fieldsToUpdate,
|
|
||||||
objectMetadata,
|
|
||||||
);
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: workflowActionInput.objectName,
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: previousObjectRecord.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: previousObjectRecord,
|
|
||||||
after: updatedObjectRecord,
|
|
||||||
updatedFields: workflowActionInput.fieldsToUpdate,
|
|
||||||
diff,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,36 +1,30 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { Repository } from 'typeorm';
|
|
||||||
import { v4 } from 'uuid';
|
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
|
||||||
import { StepStatus } from 'twenty-shared/workflow';
|
import { StepStatus } from 'twenty-shared/workflow';
|
||||||
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator';
|
||||||
import { objectRecordChangedValues } from 'src/engine/core-modules/event-emitter/utils/object-record-changed-values';
|
|
||||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||||
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
import { MetricsKeys } from 'src/engine/core-modules/metrics/types/metrics-keys.type';
|
||||||
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
import { RecordPositionService } from 'src/engine/core-modules/record-position/services/record-position.service';
|
||||||
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
import { ActorMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
StepOutput,
|
StepOutput,
|
||||||
WorkflowRunState,
|
WorkflowRunState,
|
||||||
WorkflowRunStatus,
|
WorkflowRunStatus,
|
||||||
WorkflowRunWorkspaceEntity,
|
WorkflowRunWorkspaceEntity,
|
||||||
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
} from 'src/modules/workflow/common/standard-objects/workflow-run.workspace-entity';
|
||||||
|
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
import { WorkflowCommonWorkspaceService } from 'src/modules/workflow/common/workspace-services/workflow-common.workspace-service';
|
||||||
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
import { WorkflowAction } from 'src/modules/workflow/workflow-executor/workflow-actions/types/workflow-action.type';
|
||||||
import {
|
import {
|
||||||
WorkflowRunException,
|
WorkflowRunException,
|
||||||
WorkflowRunExceptionCode,
|
WorkflowRunExceptionCode,
|
||||||
} from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception';
|
} from 'src/modules/workflow/workflow-runner/exceptions/workflow-run.exception';
|
||||||
import { WorkflowVersionWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
|
||||||
import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowRunWorkspaceService {
|
export class WorkflowRunWorkspaceService {
|
||||||
@ -38,9 +32,6 @@ export class WorkflowRunWorkspaceService {
|
|||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
private readonly workflowCommonWorkspaceService: WorkflowCommonWorkspaceService,
|
||||||
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
private readonly scopedWorkspaceContextFactory: ScopedWorkspaceContextFactory,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
private readonly recordPositionService: RecordPositionService,
|
private readonly recordPositionService: RecordPositionService,
|
||||||
private readonly metricsService: MetricsService,
|
private readonly metricsService: MetricsService,
|
||||||
) {}
|
) {}
|
||||||
@ -413,76 +404,6 @@ export class WorkflowRunWorkspaceService {
|
|||||||
return workflowRun;
|
return workflowRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async emitWorkflowRunUpdatedEvent({
|
|
||||||
workflowRunBefore,
|
|
||||||
updatedFields,
|
|
||||||
}: {
|
|
||||||
workflowRunBefore: WorkflowRunWorkspaceEntity;
|
|
||||||
updatedFields: string[];
|
|
||||||
}) {
|
|
||||||
const workspaceId = this.scopedWorkspaceContextFactory.create().workspaceId;
|
|
||||||
|
|
||||||
if (!workspaceId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOne({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflowRun',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
relations: ['fields'],
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!objectMetadata) {
|
|
||||||
throw new WorkflowRunException(
|
|
||||||
'Object metadata not found',
|
|
||||||
WorkflowRunExceptionCode.FAILURE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const workflowRunRepository =
|
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
|
||||||
workspaceId,
|
|
||||||
'workflowRun',
|
|
||||||
{ shouldBypassPermissionChecks: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
const workflowRunAfter = await workflowRunRepository.findOneBy({
|
|
||||||
id: workflowRunBefore.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!workflowRunAfter) {
|
|
||||||
throw new WorkflowRunException(
|
|
||||||
'WorkflowRun not found',
|
|
||||||
WorkflowRunExceptionCode.FAILURE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workflowRun',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workflowRunBefore.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
after: workflowRunAfter,
|
|
||||||
before: workflowRunBefore,
|
|
||||||
updatedFields,
|
|
||||||
diff: objectRecordChangedValues(
|
|
||||||
workflowRunBefore,
|
|
||||||
workflowRunAfter,
|
|
||||||
updatedFields,
|
|
||||||
objectMetadata,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getInitState(
|
private getInitState(
|
||||||
workflowVersion: WorkflowVersionWorkspaceEntity,
|
workflowVersion: WorkflowVersionWorkspaceEntity,
|
||||||
): WorkflowRunState | undefined {
|
): WorkflowRunState | undefined {
|
||||||
@ -538,14 +459,5 @@ export class WorkflowRunWorkspaceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate);
|
await workflowRunRepository.update(workflowRunToUpdate.id, partialUpdate);
|
||||||
|
|
||||||
const updatedFields = Object.keys(partialUpdate);
|
|
||||||
|
|
||||||
if (updatedFields.length > 0) {
|
|
||||||
await this.emitWorkflowRunUpdatedEvent({
|
|
||||||
workflowRunBefore: workflowRunToUpdate,
|
|
||||||
updatedFields: updatedFields,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
|
|||||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
import { WorkflowVersionStatus } from 'src/modules/workflow/common/standard-objects/workflow-version.workspace-entity';
|
||||||
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
import { WorkflowStatus } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||||
import {
|
import {
|
||||||
@ -54,10 +53,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
findOneOrFail: jest.fn(),
|
findOneOrFail: jest.fn(),
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockWorkspaceEventEmitter = {
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
@ -70,10 +65,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
provide: ServerlessFunctionService,
|
provide: ServerlessFunctionService,
|
||||||
useValue: mockServerlessFunctionService,
|
useValue: mockServerlessFunctionService,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: mockWorkspaceEventEmitter,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
provide: getRepositoryToken(ObjectMetadataEntity, 'core'),
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -125,9 +116,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
|
|
||||||
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1);
|
expect(mockWorkflowRepository.findOneOrFail).toHaveBeenCalledTimes(1);
|
||||||
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
|
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
|
||||||
expect(
|
|
||||||
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
|
|
||||||
).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('when no draft yet, update statuses', async () => {
|
it('when no draft yet, update statuses', async () => {
|
||||||
@ -155,9 +143,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
{ id: '1' },
|
{ id: '1' },
|
||||||
{ statuses: [WorkflowStatus.DRAFT, WorkflowStatus.ACTIVE] },
|
{ statuses: [WorkflowStatus.DRAFT, WorkflowStatus.ACTIVE] },
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -202,9 +187,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
mockWorkflowVersionRepository.findOneOrFail,
|
mockWorkflowVersionRepository.findOneOrFail,
|
||||||
).toHaveBeenCalledTimes(1);
|
).toHaveBeenCalledTimes(1);
|
||||||
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
|
expect(mockWorkflowRepository.update).toHaveBeenCalledTimes(0);
|
||||||
expect(
|
|
||||||
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
|
|
||||||
).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when WorkflowVersionStatus.DRAFT to WorkflowVersionStatus.ACTIVE, should activate and publish serverless functions', async () => {
|
test('when WorkflowVersionStatus.DRAFT to WorkflowVersionStatus.ACTIVE, should activate and publish serverless functions', async () => {
|
||||||
@ -286,9 +268,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
{ id: '1' },
|
{ id: '1' },
|
||||||
{ statuses: [WorkflowStatus.ACTIVE] },
|
{ statuses: [WorkflowStatus.ACTIVE] },
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -338,9 +317,6 @@ describe('WorkflowStatusesUpdate', () => {
|
|||||||
{ id: '1' },
|
{ id: '1' },
|
||||||
{ statuses: [] },
|
{ statuses: [] },
|
||||||
);
|
);
|
||||||
expect(
|
|
||||||
mockWorkspaceEventEmitter.emitDatabaseBatchEvent,
|
|
||||||
).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,20 +1,15 @@
|
|||||||
import { Logger, Scope } from '@nestjs/common';
|
import { Logger, Scope } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import isEqual from 'lodash.isequal';
|
import isEqual from 'lodash.isequal';
|
||||||
import { isDefined } from 'twenty-shared/utils';
|
import { isDefined } from 'twenty-shared/utils';
|
||||||
import { In, Repository } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
import { DatabaseEventAction } from 'src/engine/api/graphql/graphql-query-runner/enums/database-event-action';
|
|
||||||
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
import { Process } from 'src/engine/core-modules/message-queue/decorators/process.decorator';
|
||||||
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
import { Processor } from 'src/engine/core-modules/message-queue/decorators/processor.decorator';
|
||||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
|
||||||
import { ServerlessFunctionEntity } from 'src/engine/metadata-modules/serverless-function/serverless-function.entity';
|
|
||||||
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
import { ServerlessFunctionService } from 'src/engine/metadata-modules/serverless-function/serverless-function.service';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
import {
|
import {
|
||||||
WorkflowVersionStatus,
|
WorkflowVersionStatus,
|
||||||
WorkflowVersionWorkspaceEntity,
|
WorkflowVersionWorkspaceEntity,
|
||||||
@ -72,22 +67,10 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly serverlessFunctionService: ServerlessFunctionService,
|
private readonly serverlessFunctionService: ServerlessFunctionService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
@InjectRepository(ServerlessFunctionEntity, 'core')
|
|
||||||
private readonly serverlessFunctionRepository: Repository<ServerlessFunctionEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Process(WorkflowStatusesUpdateJob.name)
|
@Process(WorkflowStatusesUpdateJob.name)
|
||||||
async handle(event: WorkflowVersionBatchEvent): Promise<void> {
|
async handle(event: WorkflowVersionBatchEvent): Promise<void> {
|
||||||
const workflowObjectMetadata =
|
|
||||||
await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflow',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
case WorkflowVersionEventType.CREATE:
|
case WorkflowVersionEventType.CREATE:
|
||||||
case WorkflowVersionEventType.DELETE:
|
case WorkflowVersionEventType.DELETE:
|
||||||
@ -95,7 +78,6 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
event.workflowIds.map((workflowId) =>
|
event.workflowIds.map((workflowId) =>
|
||||||
this.handleWorkflowVersionCreatedOrDeleted({
|
this.handleWorkflowVersionCreatedOrDeleted({
|
||||||
workflowId,
|
workflowId,
|
||||||
workflowObjectMetadata,
|
|
||||||
workspaceId: event.workspaceId,
|
workspaceId: event.workspaceId,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -106,7 +88,6 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
event.statusUpdates.map((statusUpdate) =>
|
event.statusUpdates.map((statusUpdate) =>
|
||||||
this.handleWorkflowVersionStatusUpdated({
|
this.handleWorkflowVersionStatusUpdated({
|
||||||
statusUpdate,
|
statusUpdate,
|
||||||
workflowObjectMetadata,
|
|
||||||
workspaceId: event.workspaceId,
|
workspaceId: event.workspaceId,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
@ -119,11 +100,9 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
|
|
||||||
private async handleWorkflowVersionCreatedOrDeleted({
|
private async handleWorkflowVersionCreatedOrDeleted({
|
||||||
workflowId,
|
workflowId,
|
||||||
workflowObjectMetadata,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}: {
|
}: {
|
||||||
workflowId: string;
|
workflowId: string;
|
||||||
workflowObjectMetadata: ObjectMetadataEntity;
|
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
@ -163,13 +142,6 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
statuses: newWorkflowStatuses,
|
statuses: newWorkflowStatuses,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.emitWorkflowStatusUpdatedEvent({
|
|
||||||
currentWorkflow: previousWorkflow,
|
|
||||||
workflowObjectMetadata,
|
|
||||||
newWorkflowStatuses,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handlePublishServerlessFunction({
|
private async handlePublishServerlessFunction({
|
||||||
@ -222,11 +194,9 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
|
|
||||||
private async handleWorkflowVersionStatusUpdated({
|
private async handleWorkflowVersionStatusUpdated({
|
||||||
statusUpdate,
|
statusUpdate,
|
||||||
workflowObjectMetadata,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
}: {
|
}: {
|
||||||
statusUpdate: WorkflowVersionStatusUpdate;
|
statusUpdate: WorkflowVersionStatusUpdate;
|
||||||
workflowObjectMetadata: ObjectMetadataEntity;
|
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
const workflowRepository =
|
const workflowRepository =
|
||||||
@ -277,51 +247,6 @@ export class WorkflowStatusesUpdateJob {
|
|||||||
statuses: newWorkflowStatuses,
|
statuses: newWorkflowStatuses,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.emitWorkflowStatusUpdatedEvent({
|
|
||||||
currentWorkflow: workflow,
|
|
||||||
workflowObjectMetadata,
|
|
||||||
newWorkflowStatuses,
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private emitWorkflowStatusUpdatedEvent({
|
|
||||||
currentWorkflow,
|
|
||||||
workflowObjectMetadata,
|
|
||||||
newWorkflowStatuses,
|
|
||||||
workspaceId,
|
|
||||||
}: {
|
|
||||||
currentWorkflow: WorkflowWorkspaceEntity;
|
|
||||||
workflowObjectMetadata: ObjectMetadataEntity;
|
|
||||||
newWorkflowStatuses: WorkflowStatus[];
|
|
||||||
workspaceId: string;
|
|
||||||
}) {
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: workflowObjectMetadata.nameSingular,
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: currentWorkflow.id,
|
|
||||||
objectMetadata: workflowObjectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: currentWorkflow,
|
|
||||||
after: {
|
|
||||||
...currentWorkflow,
|
|
||||||
statuses: newWorkflowStatuses,
|
|
||||||
},
|
|
||||||
updatedFields: ['statuses'],
|
|
||||||
diff: {
|
|
||||||
statuses: {
|
|
||||||
before: currentWorkflow.statuses,
|
|
||||||
after: newWorkflowStatuses,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getWorkflowStatuses({
|
private async getWorkflowStatuses({
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { t } from '@lingui/core/macro';
|
import { t } from '@lingui/core/macro';
|
||||||
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 { 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 { WorkspaceEntityManager } from 'src/engine/twenty-orm/entity-manager/workspace-entity-manager';
|
||||||
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
import { ScopedWorkspaceContextFactory } from 'src/engine/twenty-orm/factories/scoped-workspace-context.factory';
|
||||||
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
@ -43,8 +39,6 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
private readonly workflowRunnerWorkspaceService: WorkflowRunnerWorkspaceService,
|
||||||
private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService,
|
private readonly automatedTriggerWorkspaceService: AutomatedTriggerWorkspaceService,
|
||||||
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
private readonly workspaceEventEmitter: WorkspaceEventEmitter,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'core')
|
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private getWorkspaceId() {
|
private getWorkspaceId() {
|
||||||
@ -412,33 +406,6 @@ export class WorkflowTriggerWorkspaceService {
|
|||||||
newStatus: WorkflowVersionStatus,
|
newStatus: WorkflowVersionStatus,
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
) {
|
) {
|
||||||
const objectMetadata = await this.objectMetadataRepository.findOneOrFail({
|
|
||||||
where: {
|
|
||||||
nameSingular: 'workflowVersion',
|
|
||||||
workspaceId,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitDatabaseBatchEvent({
|
|
||||||
objectMetadataNameSingular: 'workflowVersion',
|
|
||||||
action: DatabaseEventAction.UPDATED,
|
|
||||||
events: [
|
|
||||||
{
|
|
||||||
recordId: workflowVersion.id,
|
|
||||||
objectMetadata,
|
|
||||||
properties: {
|
|
||||||
before: workflowVersion,
|
|
||||||
after: { ...workflowVersion, status: newStatus },
|
|
||||||
updatedFields: ['status'],
|
|
||||||
diff: {
|
|
||||||
status: { before: workflowVersion.status, after: newStatus },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.workspaceEventEmitter.emitCustomBatchEvent<WorkflowVersionStatusUpdate>(
|
this.workspaceEventEmitter.emitCustomBatchEvent<WorkflowVersionStatusUpdate>(
|
||||||
WORKFLOW_VERSION_STATUS_UPDATED,
|
WORKFLOW_VERSION_STATUS_UPDATED,
|
||||||
[
|
[
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
|
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
|
||||||
import {
|
import {
|
||||||
TEST_PERSON_1_ID,
|
TEST_PERSON_1_ID,
|
||||||
TEST_PERSON_2_ID,
|
TEST_PERSON_2_ID,
|
||||||
TEST_PERSON_3_ID,
|
TEST_PERSON_3_ID,
|
||||||
} from 'test/integration/constants/test-person-ids.constants';
|
} from 'test/integration/constants/test-person-ids.constants';
|
||||||
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
|
|
||||||
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
|
import { createManyOperationFactory } from 'test/integration/graphql/utils/create-many-operation-factory.util';
|
||||||
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
import { createOneOperationFactory } from 'test/integration/graphql/utils/create-one-operation-factory.util';
|
||||||
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
|
import { deleteManyOperationFactory } from 'test/integration/graphql/utils/delete-many-operation-factory.util';
|
||||||
@ -15,8 +15,8 @@ import { findOneOperationFactory } from 'test/integration/graphql/utils/find-one
|
|||||||
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
|
||||||
import { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util';
|
import { updateManyOperationFactory } from 'test/integration/graphql/utils/update-many-operation-factory.util';
|
||||||
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
|
import { updateOneOperationFactory } from 'test/integration/graphql/utils/update-one-operation-factory.util';
|
||||||
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
|
||||||
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
|
import { deleteAllRecords } from 'test/integration/utils/delete-all-records';
|
||||||
|
import { generateRecordName } from 'test/integration/utils/generate-record-name';
|
||||||
|
|
||||||
describe('people resolvers (integration)', () => {
|
describe('people resolvers (integration)', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metada
|
|||||||
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||||
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
|
|
||||||
|
|
||||||
export interface AgentToolTestContext {
|
export interface AgentToolTestContext {
|
||||||
module: TestingModule;
|
module: TestingModule;
|
||||||
@ -64,14 +63,6 @@ export const createAgentToolTestModule =
|
|||||||
getRepositoryForWorkspace: jest.fn(),
|
getRepositoryForWorkspace: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: WorkspaceEventEmitter,
|
|
||||||
useValue: {
|
|
||||||
emit: jest.fn(),
|
|
||||||
emitDatabaseBatchEvent: jest.fn(),
|
|
||||||
emitCustomBatchEvent: jest.fn(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: WorkspacePermissionsCacheService,
|
provide: WorkspacePermissionsCacheService,
|
||||||
useValue: {
|
useValue: {
|
||||||
|
|||||||
@ -57751,7 +57751,7 @@ __metadata:
|
|||||||
|
|
||||||
"typeorm@patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server":
|
"typeorm@patch:typeorm@0.3.20#./patches/typeorm+0.3.20.patch::locator=twenty-server%40workspace%3Apackages%2Ftwenty-server":
|
||||||
version: 0.3.20
|
version: 0.3.20
|
||||||
resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./patches/typeorm+0.3.20.patch::version=0.3.20&hash=d5f43a&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
|
resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./patches/typeorm+0.3.20.patch::version=0.3.20&hash=e61204&locator=twenty-server%40workspace%3Apackages%2Ftwenty-server"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@sqltools/formatter": "npm:^1.2.5"
|
"@sqltools/formatter": "npm:^1.2.5"
|
||||||
app-root-path: "npm:^3.1.0"
|
app-root-path: "npm:^3.1.0"
|
||||||
@ -57825,7 +57825,7 @@ __metadata:
|
|||||||
typeorm: cli.js
|
typeorm: cli.js
|
||||||
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
|
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
|
||||||
typeorm-ts-node-esm: cli-ts-node-esm.js
|
typeorm-ts-node-esm: cli-ts-node-esm.js
|
||||||
checksum: 10c0/001ec2c0dc385226c9ba0ea6058b45f1c0e264a9adaac760dc7894b4a8dc25398334d3b35d94257088b39be0516a3e5b583e3a1687635d594a086abe09d6a888
|
checksum: 10c0/2593c3ddf6b243c6be13e3d9deebad1e9ecfcbdda8bb518b7fb633077e2e20b99c1873cdc0848df8ba44c9bfb22ecfe345937aa82702a9c857ecd73b32de8373
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user