Common api - Destroy and delete queries (#15177)

Done ⬇️ 
Gql : move delete and destroy logic to common
Rest : 
- rename 'delete' handler to 'destroy'
- create soft delete handlers (named 'delete') : one and many
- create destroy many handler
- update doc

--> Rest api gains NEW soft delete one/many + destroy many capabilities

closes : https://github.com/twentyhq/core-team-issues/issues/1579
This commit is contained in:
Etienne
2025-10-22 16:35:17 +02:00
committed by GitHub
parent ce87ea88f5
commit dc05f18310
50 changed files with 1800 additions and 604 deletions
@@ -6,11 +6,12 @@ import {
import isEmpty from 'lodash.isempty';
import { type ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonCreateManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-many-query-runner/common-create-many-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
import { parseUpsertRestRequest } from 'src/engine/api/rest/input-request-parsers/upsert-parser-utils/parse-upsert-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
@@ -41,12 +42,15 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler {
authContext,
});
const records = await this.commonCreateManyQueryRunnerService.run({
args: { data, selectedFields, upsert },
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
});
const records = await this.commonCreateManyQueryRunnerService.execute(
{ data, selectedFields, upsert },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.CREATE_MANY,
);
return this.formatRestResponse(
records,
@@ -61,7 +65,7 @@ export class RestApiCreateManyHandler extends RestApiBaseHandler {
records: ObjectRecord[],
objectNamePlural: string,
) {
return { data: { [objectNamePlural]: records } };
return { data: { [`create${capitalize(objectNamePlural)}`]: records } };
}
private parseRequestArgs(request: AuthenticatedRequest) {
@@ -6,11 +6,12 @@ import {
import isEmpty from 'lodash.isempty';
import { ObjectRecord } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonCreateOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-create-one-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
import { parseUpsertRestRequest } from 'src/engine/api/rest/input-request-parsers/upsert-parser-utils/parse-upsert-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
@@ -42,12 +43,15 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler {
authContext,
});
const record = await this.commonCreateOneQueryRunnerService.run({
args: { data, selectedFields, upsert },
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
});
const record = await this.commonCreateOneQueryRunnerService.execute(
{ data, selectedFields, upsert },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.CREATE_ONE,
);
return this.formatRestResponse(
record,
@@ -59,7 +63,7 @@ export class RestApiCreateOneHandler extends RestApiBaseHandler {
}
private formatRestResponse(record: ObjectRecord, objectNameSingular: string) {
return { data: { [objectNameSingular]: record } };
return { data: { [`create${capitalize(objectNameSingular)}`]: record } };
}
private parseRequestArgs(request: AuthenticatedRequest) {
@@ -0,0 +1,69 @@
import { Injectable } from '@nestjs/common';
import { ObjectRecord } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonDeleteManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-many-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
@Injectable()
export class RestApiDeleteManyHandler extends RestApiBaseHandler {
constructor(
private readonly commonDeleteManyQueryRunnerService: CommonDeleteManyQueryRunnerService,
) {
super();
}
async handle(request: AuthenticatedRequest): Promise<{
data: {
[x: string]: ObjectRecord[];
};
}> {
const { filter } = this.parseRequestArgs(request);
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
await this.buildCommonOptions(request);
try {
const records = await this.commonDeleteManyQueryRunnerService.execute(
{ filter, selectedFields: { id: true } },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.DELETE_MANY,
);
return this.formatRestResponse(
records,
objectMetadataItemWithFieldMaps.namePlural,
);
} catch (error) {
return workspaceQueryRunnerRestApiExceptionHandler(error);
}
}
private formatRestResponse(
records: ObjectRecord[],
objectNamePlural: string,
) {
return {
data: {
[`delete${capitalize(objectNamePlural)}`]: records,
},
};
}
private parseRequestArgs(request: AuthenticatedRequest) {
const filter = parseFilterRestRequest(request);
return {
filter,
};
}
}
@@ -1,43 +1,70 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ObjectRecord } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonDeleteOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-delete-one-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-fields.utils';
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
@Injectable()
export class RestApiDeleteOneHandler extends RestApiBaseHandler {
async handle(request: AuthenticatedRequest) {
const { id: recordId } = parseCorePath(request);
constructor(
private readonly commonDeleteOneQueryRunnerService: CommonDeleteOneQueryRunnerService,
) {
super();
}
if (!recordId) {
async handle(request: AuthenticatedRequest) {
try {
const { id } = this.parseRequestArgs(request);
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
} = await this.buildCommonOptions(request);
const record = await this.commonDeleteOneQueryRunnerService.execute(
{ id, selectedFields: { id: true } },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.DELETE_ONE,
);
return this.formatRestResponse(
record,
objectMetadataItemWithFieldMaps.nameSingular,
);
} catch (error) {
return workspaceQueryRunnerRestApiExceptionHandler(error);
}
}
private formatRestResponse(record: ObjectRecord, objectNameSingular: string) {
return {
data: { [`delete${capitalize(objectNameSingular)}`]: record },
};
}
private parseRequestArgs(request: AuthenticatedRequest) {
const { id } = parseCorePath(request);
if (!isDefined(id)) {
throw new BadRequestException('Record ID not found');
}
const { objectMetadata, repository, restrictedFields } =
await this.getRepositoryAndMetadataOrFail(request);
const selectOptions = getAllSelectableFields({
restrictedFields,
objectMetadata,
});
const recordToDelete = await repository.findOneOrFail({
where: { id: recordId },
select: selectOptions,
});
const columnsToReturnForDelete: string[] = [];
await repository.delete(recordId, undefined, columnsToReturnForDelete);
return this.formatResult({
operation: 'delete',
objectNameSingular: objectMetadata.objectMetadataMapItem.nameSingular,
data: {
id: recordToDelete.id,
},
});
return {
id,
depth: parseDepthRestRequest(request),
};
}
}
@@ -0,0 +1,69 @@
import { Injectable } from '@nestjs/common';
import { ObjectRecord } from 'twenty-shared/types';
import { capitalize } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonDestroyManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-many-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
@Injectable()
export class RestApiDestroyManyHandler extends RestApiBaseHandler {
constructor(
private readonly commonDestroyManyQueryRunnerService: CommonDestroyManyQueryRunnerService,
) {
super();
}
async handle(request: AuthenticatedRequest): Promise<{
data: {
[x: string]: ObjectRecord[];
};
}> {
const { filter } = this.parseRequestArgs(request);
const { authContext, objectMetadataItemWithFieldMaps, objectMetadataMaps } =
await this.buildCommonOptions(request);
try {
const records = await this.commonDestroyManyQueryRunnerService.execute(
{ filter, selectedFields: { id: true } },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.DESTROY_MANY,
);
return this.formatRestResponse(
records,
objectMetadataItemWithFieldMaps.namePlural,
);
} catch (error) {
return workspaceQueryRunnerRestApiExceptionHandler(error);
}
}
private formatRestResponse(
records: ObjectRecord[],
objectNamePlural: string,
) {
return {
data: {
[`delete${capitalize(objectNamePlural)}`]: records,
},
};
}
private parseRequestArgs(request: AuthenticatedRequest) {
const filter = parseFilterRestRequest(request);
return {
filter,
};
}
}
@@ -0,0 +1,100 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ObjectRecord } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonDestroyOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-destroy-one-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { workspaceQueryRunnerRestApiExceptionHandler } from 'src/engine/api/rest/utils/workspace-query-runner-rest-api-exception-handler.util';
import { getAllSelectableFields } from 'src/engine/api/utils/get-all-selectable-fields.utils';
@Injectable()
export class RestApiDestroyOneHandler extends RestApiBaseHandler {
constructor(
private readonly commonDestroyOneQueryRunnerService: CommonDestroyOneQueryRunnerService,
) {
super();
}
async commonHandle(request: AuthenticatedRequest) {
try {
const { id } = this.parseRequestArgs(request);
const {
authContext,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
} = await this.buildCommonOptions(request);
const record = await this.commonDestroyOneQueryRunnerService.execute(
{ id, selectedFields: { id: true } },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.DESTROY_ONE,
);
return this.formatRestResponse(
record,
objectMetadataItemWithFieldMaps.nameSingular,
);
} catch (error) {
workspaceQueryRunnerRestApiExceptionHandler(error);
}
}
private formatRestResponse(record: ObjectRecord, objectNameSingular: string) {
return { data: { [`delete${capitalize(objectNameSingular)}`]: record } };
}
private parseRequestArgs(request: AuthenticatedRequest) {
const { id } = parseCorePath(request);
if (!isDefined(id)) {
throw new BadRequestException('Record ID not found');
}
return {
id,
};
}
async handle(request: AuthenticatedRequest) {
const { id: recordId } = parseCorePath(request);
if (!recordId) {
throw new BadRequestException('Record ID not found');
}
const { objectMetadata, repository, restrictedFields } =
await this.getRepositoryAndMetadataOrFail(request);
const selectOptions = getAllSelectableFields({
restrictedFields,
objectMetadata,
});
const recordToDelete = await repository.findOneOrFail({
where: { id: recordId },
select: selectOptions,
});
const columnsToReturnForDelete: string[] = [];
await repository.delete(recordId, undefined, columnsToReturnForDelete);
return this.formatResult({
operation: 'delete',
objectNameSingular: objectMetadata.objectMetadataMapItem.nameSingular,
data: {
id: recordToDelete.id,
},
});
}
}
@@ -8,6 +8,7 @@ import {
} from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonFindManyQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-many-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
import { parseEndingBeforeRestRequest } from 'src/engine/api/rest/input-request-parsers/ending-before-parser-utils/parse-ending-before-rest-request.util';
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
@@ -110,12 +111,15 @@ export class RestApiFindManyHandler extends RestApiBaseHandler {
});
const { records, aggregatedValues, pageInfo } =
await this.commonFindManyQueryRunnerService.run({
args: { ...parsedArgs, selectedFields },
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
});
await this.commonFindManyQueryRunnerService.execute(
{ ...parsedArgs, selectedFields },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.FIND_MANY,
);
return this.formatRestResponse(
records,
@@ -6,6 +6,7 @@ import { isDefined } from 'twenty-shared/utils';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonFindOneQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-find-one-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { parseDepthRestRequest } from 'src/engine/api/rest/input-request-parsers/depth-parser-utils/parse-depth-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
@@ -73,12 +74,15 @@ export class RestApiFindOneHandler extends RestApiBaseHandler {
authContext,
});
const record = await this.commonFindOneQueryRunnerService.run({
args: { filter, selectedFields },
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
});
const record = await this.commonFindOneQueryRunnerService.execute(
{ filter, selectedFields },
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.FIND_ONE,
);
return this.formatRestResponse(
record,
@@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
import { RestApiBaseHandler } from 'src/engine/api/rest/core/interfaces/rest-api-base.handler';
import { CommonGroupByQueryRunnerService } from 'src/engine/api/common/common-query-runners/common-group-by-query-runner.service';
import { CommonQueryNames } from 'src/engine/api/common/types/common-query-args.type';
import { parseAggregateFieldsRestRequest } from 'src/engine/api/rest/input-request-parsers/aggregate-fields-parser-utils/parse-aggregate-fields-rest-request.util';
import { parseFilterRestRequest } from 'src/engine/api/rest/input-request-parsers/filter-parser-utils/parse-filter-rest-request.util';
import { parseGroupByRestRequest } from 'src/engine/api/rest/input-request-parsers/group-by-parser-utils/parse-group-by-rest-request.util';
@@ -30,18 +31,21 @@ export class RestApiGroupByHandler extends RestApiBaseHandler {
const { filter, orderBy, viewId, groupBy, selectedFields } =
this.parseRequestArgs(request);
return await this.commonGroupByQueryRunnerService.run({
args: {
return await this.commonGroupByQueryRunnerService.execute(
{
filter,
orderBy,
viewId,
groupBy,
selectedFields,
},
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
});
{
authContext,
objectMetadataMaps,
objectMetadataItemWithFieldMaps,
},
CommonQueryNames.GROUP_BY,
);
} catch (error) {
throw workspaceQueryRunnerRestApiExceptionHandler(error);
}
@@ -593,7 +593,7 @@ export abstract class RestApiBaseHandler {
objectMetadataMaps,
}: {
authContext: WorkspaceAuthContext;
depth: Depth;
depth?: Depth | undefined;
objectMetadataMapItem: ObjectMetadataItemWithFieldMaps;
objectMetadataMaps: ObjectMetadataMaps;
}) {
@@ -5,7 +5,10 @@ import { CoreCommonApiModule } from 'src/engine/api/common/core-common-api.modul
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 { RestApiDeleteManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-many.handler';
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
import { RestApiDestroyManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-destroy-many.handler';
import { RestApiDestroyOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-destroy-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';
@@ -28,14 +31,17 @@ import { TwentyORMModule } from 'src/engine/twenty-orm/twenty-orm.module';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
const restApiCoreResolvers = [
RestApiDeleteOneHandler,
RestApiCreateOneHandler,
RestApiCreateManyHandler,
RestApiUpdateOneHandler,
RestApiFindOneHandler,
RestApiFindManyHandler,
RestApiFindDuplicatesHandler,
RestApiGroupByHandler,
RestApiUpdateOneHandler,
RestApiDestroyOneHandler,
RestApiDestroyManyHandler,
RestApiDeleteOneHandler,
RestApiDeleteManyHandler,
];
@Module({
@@ -4,13 +4,17 @@ import { isDefined } from 'twenty-shared/utils';
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 { RestApiDeleteManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-many.handler';
import { RestApiDeleteOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-delete-one.handler';
import { RestApiDestroyManyHandler } from 'src/engine/api/rest/core/handlers/rest-api-destroy-many.handler';
import { RestApiDestroyOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-destroy-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 { RestApiGroupByHandler } from 'src/engine/api/rest/core/handlers/rest-api-group-by.handler';
import { RestApiUpdateOneHandler } from 'src/engine/api/rest/core/handlers/rest-api-update-one.handler';
import { parseCorePath } from 'src/engine/api/rest/core/query-builder/utils/path-parsers/parse-core-path.utils';
import { parseSoftDeleteRestRequest } from 'src/engine/api/rest/input-request-parsers/soft-delete-parser-utils/parse-soft-delete-rest-request.util';
import { AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
@@ -18,14 +22,17 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service
@Injectable()
export class RestApiCoreService {
constructor(
private readonly restApiDeleteOneHandler: RestApiDeleteOneHandler,
private readonly restApiCreateOneHandler: RestApiCreateOneHandler,
private readonly restApiCreateManyHandler: RestApiCreateManyHandler,
private readonly restApiUpdateOneHandler: RestApiUpdateOneHandler,
private readonly restApiFindOneHandler: RestApiFindOneHandler,
private readonly restApiFindManyHandler: RestApiFindManyHandler,
private readonly restApiFindDuplicatesHandler: RestApiFindDuplicatesHandler,
private readonly restApiGroupByHandler: RestApiGroupByHandler,
private readonly restApiUpdateOneHandler: RestApiUpdateOneHandler,
private readonly restApiDestroyOneHandler: RestApiDestroyOneHandler,
private readonly restApiDestroyManyHandler: RestApiDestroyManyHandler,
private readonly restApiDeleteOneHandler: RestApiDeleteOneHandler,
private readonly restApiDeleteManyHandler: RestApiDeleteManyHandler,
private readonly featureFlagService: FeatureFlagService,
) {}
@@ -36,10 +43,6 @@ export class RestApiCoreService {
);
}
async delete(request: AuthenticatedRequest) {
return await this.restApiDeleteOneHandler.handle(request);
}
async createOne(request: AuthenticatedRequest) {
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
@@ -60,14 +63,6 @@ export class RestApiCoreService {
}
}
async findDuplicates(request: AuthenticatedRequest) {
return await this.restApiFindDuplicatesHandler.handle(request);
}
async update(request: AuthenticatedRequest) {
return await this.restApiUpdateOneHandler.handle(request);
}
async get(request: AuthenticatedRequest) {
const { id: recordId } = parseCorePath(request);
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
@@ -98,4 +93,36 @@ export class RestApiCoreService {
);
}
}
async findDuplicates(request: AuthenticatedRequest) {
return await this.restApiFindDuplicatesHandler.handle(request);
}
async update(request: AuthenticatedRequest) {
return await this.restApiUpdateOneHandler.handle(request);
}
async delete(request: AuthenticatedRequest) {
const { id: recordId } = parseCorePath(request);
const isCommonApiEnabled = await this.isCommonApiEnabled(request);
const isSoftDelete = parseSoftDeleteRestRequest(request);
if (isCommonApiEnabled && !isSoftDelete && isDefined(recordId))
return await this.restApiDestroyOneHandler.commonHandle(request);
if (isCommonApiEnabled && !isSoftDelete && !isDefined(recordId))
return await this.restApiDestroyManyHandler.handle(request);
if (isCommonApiEnabled && isSoftDelete && isDefined(recordId))
return await this.restApiDeleteOneHandler.handle(request);
if (isCommonApiEnabled && isSoftDelete && !isDefined(recordId))
return await this.restApiDeleteManyHandler.handle(request);
if (!isCommonApiEnabled && !isSoftDelete && isDefined(recordId))
return await this.restApiDestroyOneHandler.handle(request);
throw new BadRequestException(
'Activate feature flag IS_COMMON_API_ENABLED to use Delete in the REST API',
);
}
}
@@ -0,0 +1,61 @@
import { parseSoftDeleteRestRequest } from 'src/engine/api/rest/input-request-parsers/soft-delete-parser-utils/parse-soft-delete-rest-request.util';
describe('parseSoftDeleteRestRequest', () => {
it('should return false when soft_delete query parameter is not defined', () => {
const request = {
query: {},
} as any;
const result = parseSoftDeleteRestRequest(request);
expect(result).toBe(false);
});
it('should return true when soft_delete query parameter is "true"', () => {
const request = {
query: {
soft_delete: 'true',
},
} as any;
const result = parseSoftDeleteRestRequest(request);
expect(result).toBe(true);
});
it('should return false when soft_delete query parameter is "false"', () => {
const request = {
query: {
soft_delete: 'false',
},
} as any;
const result = parseSoftDeleteRestRequest(request);
expect(result).toBe(false);
});
it('should return false when soft_delete query parameter is empty string', () => {
const request = {
query: {
soft_delete: '',
},
} as any;
const result = parseSoftDeleteRestRequest(request);
expect(result).toBe(false);
});
it('should return false when soft_delete query parameter is a boolean true', () => {
const request = {
query: {
soft_delete: true,
},
} as any;
const result = parseSoftDeleteRestRequest(request);
expect(result).toBe(false);
});
});
@@ -0,0 +1,13 @@
import { isDefined } from 'twenty-shared/utils';
import { type AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request';
export const parseSoftDeleteRestRequest = (
request: AuthenticatedRequest,
): boolean => {
if (!isDefined(request.query.soft_delete)) {
return false;
}
return request.query.soft_delete === 'true';
};