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:
@ -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 { 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 { 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 { WorkspaceQueryRunnerModule } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
@ -49,7 +48,6 @@ const graphqlQueryResolvers = [
|
||||
UserRoleModule,
|
||||
],
|
||||
providers: [
|
||||
ApiEventEmitterService,
|
||||
ProcessNestedRelationsHelper,
|
||||
ProcessNestedRelationsV2Helper,
|
||||
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 { WorkspaceDataSource } from 'src/engine/twenty-orm/datasource/workspace.datasource';
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
@ -180,8 +179,6 @@ export class ProcessNestedRelationsV2Helper {
|
||||
: 'id',
|
||||
ids: relationIds,
|
||||
limit: limit * parentObjectRecords.length,
|
||||
objectMetadataMaps,
|
||||
targetObjectMetadata,
|
||||
aggregate,
|
||||
sourceFieldName,
|
||||
});
|
||||
@ -286,8 +283,6 @@ export class ProcessNestedRelationsV2Helper {
|
||||
column,
|
||||
ids,
|
||||
limit,
|
||||
objectMetadataMaps,
|
||||
targetObjectMetadata,
|
||||
aggregate,
|
||||
sourceFieldName,
|
||||
}: {
|
||||
@ -297,8 +292,6 @@ export class ProcessNestedRelationsV2Helper {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
ids: any[];
|
||||
limit: number;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
targetObjectMetadata: ObjectMetadataItemWithFieldMaps;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
aggregate: Record<string, any>;
|
||||
sourceFieldName: string;
|
||||
@ -359,13 +352,7 @@ export class ProcessNestedRelationsV2Helper {
|
||||
.take(limit)
|
||||
.getMany();
|
||||
|
||||
const relationResults = formatResult<ObjectRecord[]>(
|
||||
result,
|
||||
targetObjectMetadata,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return { relationResults, relationAggregatedFieldsResult };
|
||||
return { relationResults: result, relationAggregatedFieldsResult };
|
||||
}
|
||||
|
||||
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 { 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 { 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 { 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';
|
||||
@ -66,8 +65,6 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
@Inject()
|
||||
protected readonly queryResultGettersFactory: QueryResultGettersFactory;
|
||||
@Inject()
|
||||
protected readonly apiEventEmitterService: ApiEventEmitterService;
|
||||
@Inject()
|
||||
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager;
|
||||
@Inject()
|
||||
protected readonly processNestedRelationsHelper: ProcessNestedRelationsHelper;
|
||||
@ -128,6 +125,7 @@ export abstract class GraphqlQueryBaseResolverService<
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
shouldBypassPermissionChecks,
|
||||
roleId,
|
||||
authContext,
|
||||
);
|
||||
|
||||
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 { 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 { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||
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 { 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 { 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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -48,7 +44,6 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
executionArgs,
|
||||
objectRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
const shouldBypassPermissionChecks = executionArgs.isExecutedByApiKey;
|
||||
@ -105,21 +100,15 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
|
||||
await this.processRecordsToUpdate({
|
||||
partialRecordsToUpdate: recordsToUpdate,
|
||||
existingRecords,
|
||||
repository: executionArgs.repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: executionArgs.options.objectMetadataMaps,
|
||||
result,
|
||||
authContext: executionArgs.options.authContext,
|
||||
});
|
||||
|
||||
await this.processRecordsToInsert({
|
||||
recordsToInsert,
|
||||
repository: executionArgs.repository,
|
||||
result,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: executionArgs.options.objectMetadataMaps,
|
||||
authContext: executionArgs.options.authContext,
|
||||
});
|
||||
|
||||
return result;
|
||||
@ -273,20 +262,14 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
|
||||
private async processRecordsToUpdate({
|
||||
partialRecordsToUpdate,
|
||||
existingRecords,
|
||||
repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
result,
|
||||
authContext,
|
||||
}: {
|
||||
partialRecordsToUpdate: Partial<ObjectRecord>[];
|
||||
existingRecords: Partial<ObjectRecord>[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
result: InsertResult;
|
||||
authContext: AuthContext;
|
||||
}): Promise<void> {
|
||||
for (const partialRecordToUpdate of partialRecordsToUpdate) {
|
||||
const recordId = partialRecordToUpdate.id as string;
|
||||
@ -298,101 +281,38 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
const formattedPartialRecordToUpdate = formatData(
|
||||
await repository.update(
|
||||
recordId,
|
||||
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.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({
|
||||
recordsToInsert,
|
||||
repository,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
result,
|
||||
authContext,
|
||||
}: {
|
||||
recordsToInsert: Partial<ObjectRecord>[];
|
||||
repository: WorkspaceRepository<ObjectLiteral>;
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
result: InsertResult;
|
||||
authContext: AuthContext;
|
||||
}): Promise<void> {
|
||||
const formattedInsertedRecords: ObjectRecord[] = [];
|
||||
|
||||
if (recordsToInsert.length > 0) {
|
||||
const insertResult = await repository.insert(recordsToInsert);
|
||||
|
||||
result.identifiers.push(...insertResult.identifiers);
|
||||
result.generatedMaps.push(...insertResult.generatedMaps);
|
||||
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(
|
||||
executionArgs: GraphqlQueryResolverExecutionArgs<CreateManyResolverArgs>,
|
||||
objectRecords: InsertResult,
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
): Promise<ObjectRecord[]> {
|
||||
const queryBuilder = executionArgs.repository.createQueryBuilder(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
@ -404,7 +324,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedUpsertedRecords = await queryBuilder
|
||||
const upsertedRecords = await queryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
@ -414,11 +334,7 @@ export class GraphqlQueryCreateManyResolverService extends GraphqlQueryBaseResol
|
||||
.take(QUERY_MAX_RECORDS)
|
||||
.getMany();
|
||||
|
||||
return formatResult<ObjectRecord[]>(
|
||||
nonFormattedUpsertedRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
return upsertedRecords as ObjectRecord[];
|
||||
}
|
||||
|
||||
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 { 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 { 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()
|
||||
export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -48,7 +46,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedUpsertedRecords = await queryBuilder
|
||||
const upsertedRecords = (await queryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
@ -56,21 +54,7 @@ export class GraphqlQueryCreateOneResolverService extends GraphqlQueryBaseResolv
|
||||
id: In(objectRecords.generatedMaps.map((record) => record.id)),
|
||||
})
|
||||
.take(QUERY_MAX_RECORDS)
|
||||
.getMany();
|
||||
|
||||
const upsertedRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedUpsertedRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitCreateEvents({
|
||||
records: structuredClone(upsertedRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
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 { 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
@ -52,30 +50,17 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
const deletedObjectRecords = await queryBuilder
|
||||
.softDelete()
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const formattedDeletedRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitDeletedEvents({
|
||||
records: structuredClone(formattedDeletedRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: formattedDeletedRecords,
|
||||
parentObjectRecords:
|
||||
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
@ -89,7 +74,7 @@ export class GraphqlQueryDeleteManyResolverService extends GraphqlQueryBaseResol
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return formattedDeletedRecords.map((record: ObjectRecord) =>
|
||||
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
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 { 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 { 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()
|
||||
export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -44,34 +42,20 @@ export class GraphqlQueryDeleteOneResolverService extends GraphqlQueryBaseResolv
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
const deletedObjectRecords = await queryBuilder
|
||||
.softDelete()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const formattedDeletedRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (formattedDeletedRecords.length === 0) {
|
||||
if (deletedObjectRecords.generatedMaps.length === 0) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Record not found',
|
||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const deletedRecord = formattedDeletedRecords[0];
|
||||
|
||||
this.apiEventEmitterService.emitDeletedEvents({
|
||||
records: structuredClone(formattedDeletedRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
const deletedRecord = deletedObjectRecords.generatedMaps[0] as ObjectRecord;
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
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 { 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';
|
||||
|
||||
@Injectable()
|
||||
@ -50,30 +48,17 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
const deletedObjectRecords = await queryBuilder
|
||||
.delete()
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const deletedRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedDeletedObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitDestroyEvents({
|
||||
records: structuredClone(deletedRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: deletedRecords,
|
||||
parentObjectRecords:
|
||||
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
@ -87,7 +72,7 @@ export class GraphqlQueryDestroyManyResolverService extends GraphqlQueryBaseReso
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return deletedRecords.map((record: ObjectRecord) =>
|
||||
return deletedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
|
||||
@ -16,8 +16,6 @@ import {
|
||||
} 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 { 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()
|
||||
export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -42,38 +40,25 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedDeletedObjectRecords = await queryBuilder
|
||||
const deletedObjectRecords = await queryBuilder
|
||||
.delete()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
if (!nonFormattedDeletedObjectRecords.affected) {
|
||||
if (!deletedObjectRecords.affected) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'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) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: deletedRecords,
|
||||
parentObjectRecords:
|
||||
deletedObjectRecords.generatedMaps as ObjectRecord[],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
@ -88,7 +73,7 @@ export class GraphqlQueryDestroyOneResolverService extends GraphqlQueryBaseResol
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: deletedRecords[0],
|
||||
objectRecord: deletedObjectRecords.generatedMaps[0] as ObjectRecord,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
take: 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 { 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 { formatData } from 'src/engine/twenty-orm/utils/format-data.util';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -67,20 +65,11 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
||||
let objectRecords: Partial<ObjectRecord>[] = [];
|
||||
|
||||
if (executionArgs.args.ids) {
|
||||
const nonFormattedObjectRecords = (await existingRecordsQueryBuilder
|
||||
objectRecords = (await existingRecordsQueryBuilder
|
||||
.where({ id: In(executionArgs.args.ids) })
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
objectRecords = formatResult(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
} else if (executionArgs.args.data && !isEmpty(executionArgs.args.data)) {
|
||||
objectRecords = formatData(
|
||||
executionArgs.args.data,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
objectRecords = executionArgs.args.data;
|
||||
}
|
||||
|
||||
const duplicateConnections: IConnection<ObjectRecord>[] = await Promise.all(
|
||||
@ -120,18 +109,12 @@ export class GraphqlQueryFindDuplicatesResolverService extends GraphqlQueryBaseR
|
||||
duplicateConditions,
|
||||
);
|
||||
|
||||
const nonFormattedDuplicates = (await duplicateRecordsQueryBuilder
|
||||
const duplicates = (await duplicateRecordsQueryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
const duplicates = formatResult<ObjectRecord[]>(
|
||||
nonFormattedDuplicates,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
return typeORMObjectRecordsParser.createConnection({
|
||||
objectRecords: duplicates,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
|
||||
@ -29,7 +29,6 @@ import {
|
||||
getPaginationInfo,
|
||||
} from 'src/engine/api/graphql/graphql-query-runner/utils/cursors.util';
|
||||
import { computeCursorArgFilter } from 'src/engine/api/utils/compute-cursor-arg-filter.utils';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -127,18 +126,12 @@ export class GraphqlQueryFindManyResolverService extends GraphqlQueryBaseResolve
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedObjectRecords = await queryBuilder
|
||||
const objectRecords = (await queryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.take(limit + 1)
|
||||
.getMany();
|
||||
|
||||
const objectRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedObjectRecords,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
.getMany()) as ObjectRecord[];
|
||||
|
||||
const { hasNextPage, hasPreviousPage } = getPaginationInfo(
|
||||
objectRecords,
|
||||
|
||||
@ -23,7 +23,6 @@ import {
|
||||
WorkspaceQueryRunnerException,
|
||||
WorkspaceQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception';
|
||||
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||
|
||||
@Injectable()
|
||||
export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -59,18 +58,12 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedObjectRecord = await queryBuilder
|
||||
const objectRecord = await queryBuilder
|
||||
.setFindOptions({
|
||||
select: columnsToSelect,
|
||||
})
|
||||
.getOne();
|
||||
|
||||
const objectRecord = formatResult<ObjectRecord>(
|
||||
nonFormattedObjectRecord,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (!objectRecord) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Record not found',
|
||||
@ -78,7 +71,7 @@ export class GraphqlQueryFindOneResolverService extends GraphqlQueryBaseResolver
|
||||
);
|
||||
}
|
||||
|
||||
const objectRecords = [objectRecord];
|
||||
const objectRecords = [objectRecord] as ObjectRecord[];
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
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 { 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
@ -52,30 +50,17 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedRestoredObjectRecords = await queryBuilder
|
||||
const restoredObjectRecords = await queryBuilder
|
||||
.restore()
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const formattedRestoredRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedRestoredObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
this.apiEventEmitterService.emitRestoreEvents({
|
||||
records: structuredClone(formattedRestoredRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: formattedRestoredRecords,
|
||||
parentObjectRecords:
|
||||
restoredObjectRecords.generatedMaps as ObjectRecord[],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
@ -89,7 +74,7 @@ export class GraphqlQueryRestoreManyResolverService extends GraphqlQueryBaseReso
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return formattedRestoredRecords.map((record: ObjectRecord) =>
|
||||
return restoredObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
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 { 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 { 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()
|
||||
export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -44,34 +42,21 @@ export class GraphqlQueryRestoreOneResolverService extends GraphqlQueryBaseResol
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedRestoredObjectRecords = await queryBuilder
|
||||
const restoredObjectRecords = await queryBuilder
|
||||
.restore()
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const formattedRestoredRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedRestoredObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
|
||||
if (formattedRestoredRecords.length === 0) {
|
||||
if (restoredObjectRecords.generatedMaps.length === 0) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Record not found',
|
||||
GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const restoredRecord = formattedRestoredRecords[0];
|
||||
|
||||
this.apiEventEmitterService.emitRestoreEvents({
|
||||
records: structuredClone(formattedRestoredRecords),
|
||||
authContext,
|
||||
objectMetadataItem: getObjectMetadataFromObjectMetadataItemWithFieldMaps(
|
||||
objectMetadataItemWithFieldMaps,
|
||||
),
|
||||
});
|
||||
const restoredRecord = restoredObjectRecords
|
||||
.generatedMaps[0] as ObjectRecord;
|
||||
|
||||
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||
|
||||
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 { 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 { 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 { 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';
|
||||
|
||||
@Injectable()
|
||||
@ -41,29 +33,6 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
||||
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(
|
||||
objectMetadataItemWithFieldMaps.nameSingular,
|
||||
objectMetadataItemWithFieldMaps.isCustom,
|
||||
@ -75,46 +44,24 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
||||
executionArgs.args.filter,
|
||||
);
|
||||
|
||||
const data = formatData(
|
||||
executionArgs.args.data,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
);
|
||||
|
||||
const columnsToReturn = buildColumnsToReturn({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
||||
.update(data)
|
||||
const updatedObjectRecords = await queryBuilder
|
||||
.update()
|
||||
.set(executionArgs.args.data)
|
||||
.returning(columnsToReturn)
|
||||
.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) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [
|
||||
...formattedExistingRecords,
|
||||
...formattedUpdatedRecords,
|
||||
],
|
||||
parentObjectRecords:
|
||||
updatedObjectRecords.generatedMaps as ObjectRecord[],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
authContext,
|
||||
@ -128,7 +75,7 @@ export class GraphqlQueryUpdateManyResolverService extends GraphqlQueryBaseResol
|
||||
const typeORMObjectRecordsParser =
|
||||
new ObjectRecordsToGraphqlConnectionHelper(objectMetadataMaps);
|
||||
|
||||
return formattedUpdatedRecords.map((record: ObjectRecord) =>
|
||||
return updatedObjectRecords.generatedMaps.map((record: ObjectRecord) =>
|
||||
typeORMObjectRecordsParser.processRecord({
|
||||
objectRecord: record,
|
||||
objectName: objectMetadataItemWithFieldMaps.nameSingular,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import isEmpty from 'lodash.isempty';
|
||||
import { QUERY_MAX_RECORDS } from 'twenty-shared/constants';
|
||||
|
||||
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 { 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 { 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()
|
||||
export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolverService<
|
||||
@ -40,73 +36,33 @@ export class GraphqlQueryUpdateOneResolverService extends GraphqlQueryBaseResolv
|
||||
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({
|
||||
select: executionArgs.graphqlQuerySelectedFieldsResult.select,
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
});
|
||||
|
||||
const nonFormattedUpdatedObjectRecords = await queryBuilder
|
||||
.update(data)
|
||||
const updatedObjectRecords = await queryBuilder
|
||||
.update()
|
||||
.set(executionArgs.args.data)
|
||||
.where({ id: executionArgs.args.id })
|
||||
.returning(columnsToReturn)
|
||||
.execute();
|
||||
|
||||
const formattedUpdatedRecords = formatResult<ObjectRecord[]>(
|
||||
nonFormattedUpdatedObjectRecords.raw,
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
);
|
||||
const updatedRecord = updatedObjectRecords.generatedMaps[0] as ObjectRecord;
|
||||
|
||||
if (formattedUpdatedRecords.length === 0) {
|
||||
if (!updatedRecord) {
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'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) {
|
||||
await this.processNestedRelationsHelper.processNestedRelations({
|
||||
objectMetadataMaps,
|
||||
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
|
||||
parentObjectRecords: [existingRecord, updatedRecord],
|
||||
parentObjectRecords: [updatedRecord],
|
||||
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
|
||||
limit: QUERY_MAX_RECORDS,
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user