11311 object create webhook should be triggered when object is created via rest api (#11336)
Fixes https://github.com/twentyhq/twenty/issues/11311
This commit is contained in:
2
packages/twenty-server/@types/express.d.ts
vendored
2
packages/twenty-server/@types/express.d.ts
vendored
@ -6,7 +6,7 @@ declare module 'express-serve-static-core' {
|
|||||||
interface Request {
|
interface Request {
|
||||||
user?: User | null;
|
user?: User | null;
|
||||||
apiKey?: ApiKeyWorkspaceEntity | null;
|
apiKey?: ApiKeyWorkspaceEntity | null;
|
||||||
workspace?: Workspace;
|
workspace: Workspace;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
workspaceMetadataVersion?: number;
|
workspaceMetadataVersion?: number;
|
||||||
workspaceMemberId?: string;
|
workspaceMemberId?: string;
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import { Request } from 'express';
|
|||||||
import { capitalize } from 'twenty-shared/utils';
|
import { capitalize } from 'twenty-shared/utils';
|
||||||
import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from 'typeorm';
|
import { ObjectLiteral, OrderByCondition, SelectQueryBuilder } from 'typeorm';
|
||||||
|
|
||||||
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
import { GraphqlQueryFilterConditionParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-filter/graphql-query-filter-condition.parser';
|
||||||
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
import { GraphqlQueryOrderFieldParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query-order/graphql-query-order.parser';
|
||||||
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';
|
||||||
@ -14,13 +16,14 @@ import { FilterInputFactory } from 'src/engine/api/rest/input-factories/filter-i
|
|||||||
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
import { LimitInputFactory } from 'src/engine/api/rest/input-factories/limit-input.factory';
|
||||||
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
|
import { OrderByInputFactory } from 'src/engine/api/rest/input-factories/order-by-input.factory';
|
||||||
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
|
import { StartingAfterInputFactory } from 'src/engine/api/rest/input-factories/starting-after-input.factory';
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
|
||||||
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
import { FieldMetadataMap } from 'src/engine/metadata-modules/types/field-metadata-map';
|
||||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||||
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
import { getObjectMetadataMapItemByNameSingular } from 'src/engine/metadata-modules/utils/get-object-metadata-map-item-by-name-singular.util';
|
||||||
import { 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 as formatGetManyData } from 'src/engine/twenty-orm/utils/format-result.util';
|
import { formatResult as formatGetManyData } from 'src/engine/twenty-orm/utils/format-result.util';
|
||||||
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
|
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
|
||||||
|
|
||||||
interface FindManyMeta {
|
interface FindManyMeta {
|
||||||
hasNextPage: boolean;
|
hasNextPage: boolean;
|
||||||
@ -44,10 +47,10 @@ export class RestApiCoreServiceV2 {
|
|||||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly limitInputFactory: LimitInputFactory,
|
private readonly limitInputFactory: LimitInputFactory,
|
||||||
private readonly filterInputFactory: FilterInputFactory,
|
private readonly filterInputFactory: FilterInputFactory,
|
||||||
private readonly featureFlagService: FeatureFlagService,
|
|
||||||
private readonly orderByInputFactory: OrderByInputFactory,
|
private readonly orderByInputFactory: OrderByInputFactory,
|
||||||
private readonly startingAfterInputFactory: StartingAfterInputFactory,
|
private readonly startingAfterInputFactory: StartingAfterInputFactory,
|
||||||
private readonly endingBeforeInputFactory: EndingBeforeInputFactory,
|
private readonly endingBeforeInputFactory: EndingBeforeInputFactory,
|
||||||
|
protected readonly apiEventEmitterService: ApiEventEmitterService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async delete(request: Request) {
|
async delete(request: Request) {
|
||||||
@ -57,7 +60,7 @@ export class RestApiCoreServiceV2 {
|
|||||||
throw new BadRequestException('Record ID not found');
|
throw new BadRequestException('Record ID not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { objectMetadataNameSingular, repository } =
|
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||||
await this.getRepositoryAndMetadataOrFail(request);
|
await this.getRepositoryAndMetadataOrFail(request);
|
||||||
const recordToDelete = await repository.findOneOrFail({
|
const recordToDelete = await repository.findOneOrFail({
|
||||||
where: { id: recordId },
|
where: { id: recordId },
|
||||||
@ -65,6 +68,12 @@ export class RestApiCoreServiceV2 {
|
|||||||
|
|
||||||
await repository.delete(recordId);
|
await repository.delete(recordId);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitDeletedEvents(
|
||||||
|
[recordToDelete],
|
||||||
|
this.getAuthContextFromRequest(request),
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
);
|
||||||
|
|
||||||
return this.formatResult({
|
return this.formatResult({
|
||||||
operation: 'delete',
|
operation: 'delete',
|
||||||
objectNameSingular: objectMetadataNameSingular,
|
objectNameSingular: objectMetadataNameSingular,
|
||||||
@ -77,10 +86,16 @@ export class RestApiCoreServiceV2 {
|
|||||||
async createOne(request: Request) {
|
async createOne(request: Request) {
|
||||||
const { body } = request;
|
const { body } = request;
|
||||||
|
|
||||||
const { objectMetadataNameSingular, repository } =
|
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||||
await this.getRepositoryAndMetadataOrFail(request);
|
await this.getRepositoryAndMetadataOrFail(request);
|
||||||
const createdRecord = await repository.save(body);
|
const createdRecord = await repository.save(body);
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitCreateEvents(
|
||||||
|
[createdRecord],
|
||||||
|
this.getAuthContextFromRequest(request),
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
);
|
||||||
|
|
||||||
return this.formatResult({
|
return this.formatResult({
|
||||||
operation: 'create',
|
operation: 'create',
|
||||||
objectNameSingular: objectMetadataNameSingular,
|
objectNameSingular: objectMetadataNameSingular,
|
||||||
@ -95,7 +110,7 @@ export class RestApiCoreServiceV2 {
|
|||||||
throw new BadRequestException('Record ID not found');
|
throw new BadRequestException('Record ID not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { objectMetadataNameSingular, repository } =
|
const { objectMetadataNameSingular, objectMetadata, repository } =
|
||||||
await this.getRepositoryAndMetadataOrFail(request);
|
await this.getRepositoryAndMetadataOrFail(request);
|
||||||
|
|
||||||
const recordToUpdate = await repository.findOneOrFail({
|
const recordToUpdate = await repository.findOneOrFail({
|
||||||
@ -107,6 +122,14 @@ export class RestApiCoreServiceV2 {
|
|||||||
...request.body,
|
...request.body,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.apiEventEmitterService.emitUpdateEvents(
|
||||||
|
[recordToUpdate],
|
||||||
|
[updatedRecord],
|
||||||
|
Object.keys(request.body),
|
||||||
|
this.getAuthContextFromRequest(request),
|
||||||
|
objectMetadata.objectMetadataMapItem,
|
||||||
|
);
|
||||||
|
|
||||||
return this.formatResult({
|
return this.formatResult({
|
||||||
operation: 'update',
|
operation: 'update',
|
||||||
objectNameSingular: objectMetadataNameSingular,
|
objectNameSingular: objectMetadataNameSingular,
|
||||||
@ -441,7 +464,7 @@ export class RestApiCoreServiceV2 {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const repository =
|
const repository =
|
||||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ObjectRecord>(
|
||||||
workspace.id,
|
workspace.id,
|
||||||
objectMetadataNameSingular,
|
objectMetadataNameSingular,
|
||||||
);
|
);
|
||||||
@ -465,4 +488,14 @@ export class RestApiCoreServiceV2 {
|
|||||||
id: isForwardPagination ? { gt: cursorData.id } : { lt: cursorData.id },
|
id: isForwardPagination ? { gt: cursorData.id } : { lt: cursorData.id },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getAuthContextFromRequest(request: Request): AuthContext {
|
||||||
|
return {
|
||||||
|
user: request.user,
|
||||||
|
workspace: request.workspace,
|
||||||
|
apiKey: request.apiKey,
|
||||||
|
workspaceMemberId: request.workspaceMemberId,
|
||||||
|
userWorkspaceId: request.userWorkspaceId,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import { HttpModule } from '@nestjs/axios';
|
import { HttpModule } from '@nestjs/axios';
|
||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
|
|
||||||
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
import { RestApiCoreBatchController } from 'src/engine/api/rest/core/controllers/rest-api-core-batch.controller';
|
||||||
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';
|
||||||
@ -17,11 +16,9 @@ import { RestApiMetadataController } from 'src/engine/api/rest/metadata/rest-api
|
|||||||
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
|
import { RestApiMetadataService } from 'src/engine/api/rest/metadata/rest-api-metadata.service';
|
||||||
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
import { RestApiService } from 'src/engine/api/rest/rest-api.service';
|
||||||
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
|
||||||
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
|
||||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
|
||||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
|
||||||
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -31,8 +28,6 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
|||||||
AuthModule,
|
AuthModule,
|
||||||
HttpModule,
|
HttpModule,
|
||||||
TwentyORMModule,
|
TwentyORMModule,
|
||||||
TypeOrmModule.forFeature([FeatureFlag], 'core'),
|
|
||||||
FeatureFlagModule,
|
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
RestApiMetadataController,
|
RestApiMetadataController,
|
||||||
@ -44,12 +39,12 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
|
|||||||
RestApiCoreService,
|
RestApiCoreService,
|
||||||
RestApiCoreServiceV2,
|
RestApiCoreServiceV2,
|
||||||
RestApiService,
|
RestApiService,
|
||||||
FeatureFlagService,
|
|
||||||
StartingAfterInputFactory,
|
StartingAfterInputFactory,
|
||||||
EndingBeforeInputFactory,
|
EndingBeforeInputFactory,
|
||||||
LimitInputFactory,
|
LimitInputFactory,
|
||||||
FilterInputFactory,
|
FilterInputFactory,
|
||||||
OrderByInputFactory,
|
OrderByInputFactory,
|
||||||
|
ApiEventEmitterService,
|
||||||
],
|
],
|
||||||
exports: [RestApiMetadataService],
|
exports: [RestApiMetadataService],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user