Common - Update one/many, restore one/many, findDuplicates & mergeMany (#15279)

closes https://github.com/twentyhq/core-team-issues/issues/1739
This commit is contained in:
Etienne
2025-10-24 14:41:17 +02:00
committed by GitHub
parent 8f60f7e277
commit 10f4f1592f
60 changed files with 2346 additions and 74 deletions
@@ -28,6 +28,9 @@ import {
computeBatchPath,
computeDuplicatesResultPath,
computeManyResultPath,
computeMergeManyResultPath,
computeRestoreManyResultPath,
computeRestoreOneResultPath,
computeSingleResultPath,
} from 'src/engine/core-modules/open-api/utils/path.utils';
import {
@@ -43,12 +46,12 @@ import {
} from 'src/engine/core-modules/open-api/utils/responses.utils';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
import { shouldExcludeFromWorkspaceApi } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/should-exclude-from-workspace-api.util';
import { getServerUrl } from 'src/utils/get-server-url';
import { WorkspaceNotFoundDefaultError } from 'src/engine/core-modules/workspace/workspace.exception';
@Injectable()
export class OpenApiService {
@@ -125,6 +128,10 @@ export class OpenApiService {
paths[`/${item.namePlural}/{id}`] = computeSingleResultPath(item);
paths[`/${item.namePlural}/duplicates`] =
computeDuplicatesResultPath(item);
paths[`/restore/${item.namePlural}/{id}`] =
computeRestoreOneResultPath(item);
paths[`/restore/${item.namePlural}`] = computeRestoreManyResultPath(item);
paths[`/${item.namePlural}/merge`] = computeMergeManyResultPath(item);
return paths;
}, schema.paths as OpenAPIV3_1.PathsObject);
@@ -4,6 +4,7 @@ import { capitalize } from 'twenty-shared/utils';
import {
getArrayRequestBody,
getFindDuplicatesRequestBody,
getMergeManyRequestBody,
getRequestBody,
getUpdateRequestBody,
} from 'src/engine/core-modules/open-api/utils/request-body.utils';
@@ -16,6 +17,10 @@ import {
getFindManyResponse200,
getFindOneResponse200,
getJsonResponse,
getMergeManyResponse200,
getRestoreManyResponse200,
getRestoreOneResponse200,
getUpdateManyResponse200,
getUpdateOneResponse200,
} from 'src/engine/core-modules/open-api/utils/responses.utils';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -94,6 +99,21 @@ export const computeManyResultPath = (
'401': { $ref: '#/components/responses/401' },
},
},
patch: {
tags: [item.namePlural],
summary: `Update Many ${item.namePlural}`,
operationId: `updateMany${capitalize(item.namePlural)}`,
parameters: [
{ $ref: '#/components/parameters/depth' },
{ $ref: '#/components/parameters/filter' },
],
requestBody: getUpdateRequestBody(capitalize(item.nameSingular)),
responses: {
'200': getUpdateManyResponse200(item),
'400': { $ref: '#/components/responses/400' },
'401': { $ref: '#/components/responses/401' },
},
},
} as OpenAPIV3_1.PathItemObject;
};
@@ -187,3 +207,64 @@ export const computeDuplicatesResultPath = (
},
} as OpenAPIV3_1.PathItemObject;
};
export const computeRestoreOneResultPath = (
item: ObjectMetadataEntity,
): OpenAPIV3_1.PathItemObject => {
return {
patch: {
tags: [item.namePlural],
summary: `Restore One ${item.nameSingular}`,
operationId: `restoreOne${capitalize(item.nameSingular)}`,
parameters: [
{ $ref: '#/components/parameters/idPath' },
{ $ref: '#/components/parameters/depth' },
],
responses: {
'200': getRestoreOneResponse200(item),
'400': { $ref: '#/components/responses/400' },
'401': { $ref: '#/components/responses/401' },
},
},
} as OpenAPIV3_1.PathItemObject;
};
export const computeRestoreManyResultPath = (
item: ObjectMetadataEntity,
): OpenAPIV3_1.PathItemObject => {
return {
patch: {
tags: [item.namePlural],
summary: `Restore Many ${item.namePlural}`,
operationId: `restoreMany${capitalize(item.namePlural)}`,
parameters: [
{ $ref: '#/components/parameters/filter' },
{ $ref: '#/components/parameters/depth' },
],
responses: {
'200': getRestoreManyResponse200(item),
'400': { $ref: '#/components/responses/400' },
'401': { $ref: '#/components/responses/401' },
},
},
} as OpenAPIV3_1.PathItemObject;
};
export const computeMergeManyResultPath = (
item: ObjectMetadataEntity,
): OpenAPIV3_1.PathItemObject => {
return {
patch: {
tags: [item.namePlural],
summary: `Merge Many ${item.namePlural}`,
operationId: `mergeMany${capitalize(item.namePlural)}`,
parameters: [{ $ref: '#/components/parameters/depth' }],
requestBody: getMergeManyRequestBody(),
responses: {
'200': getMergeManyResponse200(item),
'400': { $ref: '#/components/responses/400' },
'401': { $ref: '#/components/responses/401' },
},
},
} as OpenAPIV3_1.PathItemObject;
};
@@ -73,3 +73,40 @@ export const getFindDuplicatesRequestBody = (name: string) => {
},
};
};
export const getMergeManyRequestBody = () => {
return {
description: 'body',
required: true,
content: {
'application/json': {
schema: {
type: 'object',
properties: {
ids: {
type: 'array',
description: 'The IDs of the records to merge',
items: {
type: 'string',
format: 'uuid',
},
},
conflictPriorityIndex: {
type: 'number',
description:
'The index of the record to use when conflicts occur',
},
dryRun: {
description:
'If true, the merge will not be performed and a preview of the merge will be returned.',
type: 'boolean',
default: false,
},
},
example: { ids: [v4()], conflictPriorityIndex: 0, dryRun: false },
required: ['ids', 'conflictPriorityIndex'],
},
},
},
};
};
@@ -81,6 +81,65 @@ export const getFindOneResponse200 = (
};
};
export const getRestoreOneResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
) => {
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)}ForResponse`;
return {
description: 'Successful operation',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
data: {
type: 'object',
properties: {
[`restore${capitalize(item.nameSingular)}`]: {
$ref: schemaRef,
},
},
},
},
},
},
},
};
};
export const getRestoreManyResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
) => {
const schemaRef = `#/components/schemas/${capitalize(
item.nameSingular,
)}ForResponse`;
return {
description: 'Successful operation',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
data: {
type: 'object',
properties: {
[`restore${capitalize(item.namePlural)}`]: {
type: 'array',
items: {
$ref: schemaRef,
},
},
},
},
},
},
},
},
};
};
export const getCreateOneResponse201 = (
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
fromMetadata = false,
@@ -208,6 +267,36 @@ export const getDeleteManyResponse200 = (
};
};
export const getUpdateManyResponse200 = (
item: Pick<ObjectMetadataEntity, 'namePlural' | 'nameSingular'>,
) => {
const schemaRef = `#/components/schemas/${capitalize(item.nameSingular)}ForResponse`;
return {
description: 'Successful operation',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
data: {
type: 'object',
properties: {
[`update${capitalize(item.namePlural)}`]: {
type: 'array',
items: {
$ref: schemaRef,
},
},
},
},
},
},
},
},
};
};
export const getDeleteResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular'>,
fromMetadata = false,
@@ -350,3 +439,32 @@ export const getFindDuplicatesResponse200 = (
},
};
};
export const getMergeManyResponse200 = (
item: Pick<ObjectMetadataEntity, 'nameSingular' | 'namePlural'>,
) => {
const schemaRef = `#/components/schemas/${capitalize(
item.nameSingular,
)}ForResponse`;
return {
description: 'Successful operation',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
data: {
type: 'object',
properties: {
[`merge${capitalize(item.namePlural)}`]: {
$ref: schemaRef,
},
},
},
},
},
},
},
};
};