Spaces:
Build error
Build error
twenty / packages /twenty-server /src /engine /metadata-modules /object-metadata /controllers /object-metadata.controller.ts
| import { | |
| Body, | |
| Controller, | |
| Delete, | |
| Get, | |
| Param, | |
| ParseUUIDPipe, | |
| Patch, | |
| Post, | |
| Put, | |
| Req, | |
| UseFilters, | |
| UseGuards, | |
| UsePipes, | |
| ValidationPipe, | |
| } from '@nestjs/common'; | |
| import { InjectRepository } from '@nestjs/typeorm'; | |
| import { PermissionFlagType } from 'twenty-shared/constants'; | |
| import { FeatureFlagKey } from 'twenty-shared/types'; | |
| import { In, Repository } from 'typeorm'; | |
| import { parseEndingBeforeRestRequest } from 'src/engine/api/rest/input-request-parsers/ending-before-parser-utils/parse-ending-before-rest-request.util'; | |
| import { parseLimitRestRequest } from 'src/engine/api/rest/input-request-parsers/limit-parser-utils/parse-limit-rest-request.util'; | |
| import { parseStartingAfterRestRequest } from 'src/engine/api/rest/input-request-parsers/starting-after-parser-utils/parse-starting-after-rest-request.util'; | |
| import { | |
| paginateByIdCursor, | |
| type RestCursorPageInfo, | |
| } from 'src/engine/api/rest/metadata/utils/paginate-by-id-cursor.util'; | |
| import { type AuthenticatedRequest } from 'src/engine/api/rest/types/authenticated-request'; | |
| import { ApplicationRestApiExceptionFilter } from 'src/engine/core-modules/application/application-rest-api-exception.filter'; | |
| import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service'; | |
| import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity'; | |
| import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; | |
| import { JwtAuthGuard } from 'src/engine/guards/jwt-auth.guard'; | |
| import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard'; | |
| import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; | |
| import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; | |
| import { fromFieldMetadataEntityToFieldMetadataDto } from 'src/engine/metadata-modules/field-metadata/utils/from-field-metadata-entity-to-field-metadata-dto.util'; | |
| import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; | |
| import { fromFlatObjectMetadataToObjectMetadataDto } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-to-object-metadata-dto.util'; | |
| import { computeUniqueFieldMetadataIdsFromFlatIndexMaps } from 'src/engine/metadata-modules/index-metadata/utils/compute-unique-field-metadata-ids-from-flat-index-maps.util'; | |
| import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; | |
| import { type ObjectMetadataWithFieldsDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata-with-fields.dto'; | |
| import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; | |
| import { ObjectMetadataRestApiExceptionFilter } from 'src/engine/metadata-modules/object-metadata/filters/object-metadata-rest-api-exception.filter'; | |
| import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; | |
| import { | |
| ObjectMetadataException, | |
| ObjectMetadataExceptionCode, | |
| } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; | |
| import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; | |
| import { fromObjectMetadataEntityToObjectMetadataDto } from 'src/engine/metadata-modules/object-metadata/utils/from-object-metadata-entity-to-object-metadata-dto.util'; | |
| import { | |
| toLegacyObjectMetadataCreateResponse, | |
| toLegacyObjectMetadataDeleteResponse, | |
| toLegacyObjectMetadataFindOneResponse, | |
| toLegacyObjectMetadataListResponse, | |
| toLegacyObjectMetadataUpdateResponse, | |
| } from 'src/engine/metadata-modules/object-metadata/utils/to-legacy-object-metadata-response.util'; | |
| import { PermissionsRestApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-rest-api-exception.filter'; | |
| ('rest/metadata/objects') | |
| ( | |
| JwtAuthGuard, | |
| WorkspaceAuthGuard, | |
| SettingsPermissionGuard(PermissionFlagType.DATA_MODEL), | |
| ) | |
| ( | |
| PermissionsRestApiExceptionFilter, | |
| ObjectMetadataRestApiExceptionFilter, | |
| ApplicationRestApiExceptionFilter, | |
| ) | |
| (new ValidationPipe()) | |
| export class ObjectMetadataController { | |
| constructor( | |
| (ObjectMetadataEntity) | |
| private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, | |
| (FieldMetadataEntity) | |
| private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>, | |
| private readonly objectMetadataService: ObjectMetadataService, | |
| private readonly featureFlagService: FeatureFlagService, | |
| private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, | |
| ) {} | |
| private async loadUniqueFieldMetadataIds( | |
| workspaceId: string, | |
| ): Promise<ReadonlySet<string>> { | |
| const { flatIndexMaps } = | |
| await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( | |
| { workspaceId, flatMapsKeys: ['flatIndexMaps'] }, | |
| ); | |
| return computeUniqueFieldMetadataIdsFromFlatIndexMaps(flatIndexMaps); | |
| } | |
| () | |
| async findMany( | |
| () request: AuthenticatedRequest, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| const { items, pageInfo, totalCount } = await paginateByIdCursor({ | |
| repository: this.objectMetadataRepository, | |
| workspaceId, | |
| limit: parseLimitRestRequest(request), | |
| startingAfter: parseStartingAfterRestRequest(request), | |
| endingBefore: parseEndingBeforeRestRequest(request), | |
| }); | |
| const [fields, uniqueFieldMetadataIds] = await Promise.all([ | |
| this.findFieldsForObjectIds( | |
| workspaceId, | |
| items.map((object) => object.id), | |
| ), | |
| this.loadUniqueFieldMetadataIds(workspaceId), | |
| ]); | |
| const data = items.map((object) => | |
| this.toObjectWithFieldsDto( | |
| object, | |
| fields.get(object.id) ?? [], | |
| uniqueFieldMetadataIds, | |
| ), | |
| ); | |
| const result: { | |
| data: ObjectMetadataWithFieldsDTO[]; | |
| pageInfo: RestCursorPageInfo; | |
| totalCount: number; | |
| } = { data, pageInfo, totalCount }; | |
| return (await this.isNewMetadataFormat(workspaceId)) | |
| ? result | |
| : toLegacyObjectMetadataListResponse(result); | |
| } | |
| (':id') | |
| async findOne( | |
| ('id', new ParseUUIDPipe()) id: string, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| const object = await this.objectMetadataRepository.findOne({ | |
| where: { id, workspaceId }, | |
| }); | |
| if (!object) { | |
| throw new ObjectMetadataException( | |
| 'Object metadata not found', | |
| ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, | |
| ); | |
| } | |
| const [fields, uniqueFieldMetadataIds] = await Promise.all([ | |
| this.fieldMetadataRepository.find({ | |
| where: { objectMetadataId: object.id, workspaceId }, | |
| }), | |
| this.loadUniqueFieldMetadataIds(workspaceId), | |
| ]); | |
| const result = this.toObjectWithFieldsDto( | |
| object, | |
| fields, | |
| uniqueFieldMetadataIds, | |
| ); | |
| return (await this.isNewMetadataFormat(workspaceId)) | |
| ? result | |
| : toLegacyObjectMetadataFindOneResponse(result); | |
| } | |
| () | |
| async createOne( | |
| () input: CreateObjectInput, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| const flatObject = await this.objectMetadataService.createOneObject({ | |
| createObjectInput: input, | |
| workspaceId, | |
| }); | |
| const [fields, uniqueFieldMetadataIds] = await Promise.all([ | |
| this.fieldMetadataRepository.find({ | |
| where: { objectMetadataId: flatObject.id, workspaceId }, | |
| }), | |
| this.loadUniqueFieldMetadataIds(workspaceId), | |
| ]); | |
| const result: ObjectMetadataWithFieldsDTO = { | |
| ...fromFlatObjectMetadataToObjectMetadataDto(flatObject), | |
| fields: fields.map((field) => | |
| fromFieldMetadataEntityToFieldMetadataDto( | |
| field, | |
| uniqueFieldMetadataIds, | |
| ), | |
| ), | |
| }; | |
| return (await this.isNewMetadataFormat(workspaceId)) | |
| ? result | |
| : toLegacyObjectMetadataCreateResponse(result); | |
| } | |
| (':id') | |
| async updateOnePatch( | |
| ('id', new ParseUUIDPipe()) id: string, | |
| () update: UpdateObjectPayload, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| return this.handleUpdate({ id, update, workspaceId }); | |
| } | |
| (':id') | |
| async updateOnePut( | |
| ('id', new ParseUUIDPipe()) id: string, | |
| () update: UpdateObjectPayload, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| return this.handleUpdate({ id, update, workspaceId }); | |
| } | |
| (':id') | |
| async deleteOne( | |
| ('id', new ParseUUIDPipe()) id: string, | |
| () { id: workspaceId }: WorkspaceEntity, | |
| ) { | |
| const flatObject = await this.objectMetadataService.deleteOneObject({ | |
| deleteObjectInput: { id }, | |
| workspaceId, | |
| }); | |
| const result = fromFlatObjectMetadataToObjectMetadataDto(flatObject); | |
| return (await this.isNewMetadataFormat(workspaceId)) | |
| ? result | |
| : toLegacyObjectMetadataDeleteResponse(result); | |
| } | |
| private async handleUpdate({ | |
| id, | |
| update, | |
| workspaceId, | |
| }: { | |
| id: string; | |
| update: UpdateObjectPayload; | |
| workspaceId: string; | |
| }) { | |
| const flatObject = await this.objectMetadataService.updateOneObject({ | |
| updateObjectInput: { id, update }, | |
| workspaceId, | |
| }); | |
| const [fields, uniqueFieldMetadataIds] = await Promise.all([ | |
| this.fieldMetadataRepository.find({ | |
| where: { objectMetadataId: flatObject.id, workspaceId }, | |
| }), | |
| this.loadUniqueFieldMetadataIds(workspaceId), | |
| ]); | |
| const result: ObjectMetadataWithFieldsDTO = { | |
| ...fromFlatObjectMetadataToObjectMetadataDto(flatObject), | |
| fields: fields.map((field) => | |
| fromFieldMetadataEntityToFieldMetadataDto( | |
| field, | |
| uniqueFieldMetadataIds, | |
| ), | |
| ), | |
| }; | |
| return (await this.isNewMetadataFormat(workspaceId)) | |
| ? result | |
| : toLegacyObjectMetadataUpdateResponse(result); | |
| } | |
| private async isNewMetadataFormat(workspaceId: string): Promise<boolean> { | |
| return this.featureFlagService.isFeatureEnabled( | |
| FeatureFlagKey.IS_REST_METADATA_API_NEW_FORMAT_DIRECT, | |
| workspaceId, | |
| ); | |
| } | |
| private async findFieldsForObjectIds( | |
| workspaceId: string, | |
| objectIds: string[], | |
| ): Promise<Map<string, FieldMetadataEntity[]>> { | |
| const grouped = new Map<string, FieldMetadataEntity[]>(); | |
| if (objectIds.length === 0) { | |
| return grouped; | |
| } | |
| const fields = await this.fieldMetadataRepository.find({ | |
| where: { workspaceId, objectMetadataId: In(objectIds) }, | |
| }); | |
| for (const field of fields) { | |
| const list = grouped.get(field.objectMetadataId); | |
| if (list) { | |
| list.push(field); | |
| } else { | |
| grouped.set(field.objectMetadataId, [field]); | |
| } | |
| } | |
| return grouped; | |
| } | |
| private toObjectWithFieldsDto( | |
| object: ObjectMetadataEntity, | |
| fields: FieldMetadataEntity[], | |
| uniqueFieldMetadataIds: ReadonlySet<string>, | |
| ): ObjectMetadataWithFieldsDTO { | |
| return { | |
| ...fromObjectMetadataEntityToObjectMetadataDto(object), | |
| fields: fields.map((field) => | |
| fromFieldMetadataEntityToFieldMetadataDto( | |
| field, | |
| uniqueFieldMetadataIds, | |
| ), | |
| ), | |
| }; | |
| } | |
| } | |