[fix] Increment cache version after object/field/relation update (#5316)

Fixes #5276.

Updates were not triggering a cache version incrementation because they
do not trigger migrations while that is where the caching version logic
was.
We have decided to move the cache incrementation logic to the services.
This commit is contained in:
Marie
2024-05-07 16:30:25 +02:00
committed by GitHub
parent b0d1cc9dcb
commit 7c3e82870c
22 changed files with 160 additions and 39 deletions

View File

@ -27,7 +27,7 @@ const documents = {
"\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument, "\n mutation CreateOneFieldMetadataItem($input: CreateOneFieldMetadataInput!) {\n createOneField(input: $input) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n defaultValue\n options\n }\n }\n": types.CreateOneFieldMetadataItemDocument,
"\n mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) {\n createOneRelation(input: $input) {\n id\n relationType\n fromObjectMetadataId\n toObjectMetadataId\n fromFieldMetadataId\n toFieldMetadataId\n createdAt\n updatedAt\n }\n }\n": types.CreateOneRelationMetadataDocument, "\n mutation CreateOneRelationMetadata($input: CreateOneRelationInput!) {\n createOneRelation(input: $input) {\n id\n relationType\n fromObjectMetadataId\n toObjectMetadataId\n fromFieldMetadataId\n toFieldMetadataId\n createdAt\n updatedAt\n }\n }\n": types.CreateOneRelationMetadataDocument,
"\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.UpdateOneFieldMetadataItemDocument, "\n mutation UpdateOneFieldMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateFieldInput!\n ) {\n updateOneField(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.UpdateOneFieldMetadataItemDocument,
"\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument, "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.UpdateOneObjectMetadataItemDocument,
"\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument, "\n mutation DeleteOneObjectMetadataItem($idToDelete: UUID!) {\n deleteOneObject(input: { id: $idToDelete }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n": types.DeleteOneObjectMetadataItemDocument,
"\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument, "\n mutation DeleteOneFieldMetadataItem($idToDelete: UUID!) {\n deleteOneField(input: { id: $idToDelete }) {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isNullable\n createdAt\n updatedAt\n }\n }\n": types.DeleteOneFieldMetadataItemDocument,
"\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n isRemote\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n isRemote\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n relationDefinition {\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument, "\n query ObjectMetadataItems(\n $objectFilter: objectFilter\n $fieldFilter: fieldFilter\n ) {\n objects(paging: { first: 1000 }, filter: $objectFilter) {\n edges {\n node {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isRemote\n isActive\n isSystem\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n fields(paging: { first: 1000 }, filter: $fieldFilter) {\n edges {\n node {\n id\n type\n name\n label\n description\n icon\n isCustom\n isActive\n isSystem\n isNullable\n createdAt\n updatedAt\n fromRelationMetadata {\n id\n relationType\n toObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n isRemote\n }\n toFieldMetadataId\n }\n toRelationMetadata {\n id\n relationType\n fromObjectMetadata {\n id\n dataSourceId\n nameSingular\n namePlural\n isSystem\n isRemote\n }\n fromFieldMetadataId\n }\n defaultValue\n options\n relationDefinition {\n direction\n sourceObjectMetadata {\n id\n nameSingular\n namePlural\n }\n sourceFieldMetadata {\n id\n name\n }\n targetObjectMetadata {\n id\n nameSingular\n namePlural\n }\n targetFieldMetadata {\n id\n name\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n }\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n }\n }\n": types.ObjectMetadataItemsDocument,
@ -106,7 +106,7 @@ export function graphql(source: "\n mutation UpdateOneFieldMetadataItem(\n $
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */
export function graphql(source: "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectInput!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"]; export function graphql(source: "\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"): (typeof documents)["\n mutation UpdateOneObjectMetadataItem(\n $idToUpdate: UUID!\n $updatePayload: UpdateObjectPayload!\n ) {\n updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {\n id\n dataSourceId\n nameSingular\n namePlural\n labelSingular\n labelPlural\n description\n icon\n isCustom\n isActive\n createdAt\n updatedAt\n labelIdentifierFieldMetadataId\n imageIdentifierFieldMetadataId\n }\n }\n"];
/** /**
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/ */

File diff suppressed because one or more lines are too long

View File

@ -80,7 +80,7 @@ export const UPDATE_ONE_FIELD_METADATA_ITEM = gql`
export const UPDATE_ONE_OBJECT_METADATA_ITEM = gql` export const UPDATE_ONE_OBJECT_METADATA_ITEM = gql`
mutation UpdateOneObjectMetadataItem( mutation UpdateOneObjectMetadataItem(
$idToUpdate: UUID! $idToUpdate: UUID!
$updatePayload: UpdateObjectInput! $updatePayload: UpdateObjectPayload!
) { ) {
updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) { updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {
id id

View File

@ -3,7 +3,7 @@ import { gql } from '@apollo/client';
export const query = gql` export const query = gql`
mutation UpdateOneObjectMetadataItem( mutation UpdateOneObjectMetadataItem(
$idToUpdate: UUID! $idToUpdate: UUID!
$updatePayload: UpdateObjectInput! $updatePayload: UpdateObjectPayload!
) { ) {
updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) { updateOneObject(input: { id: $idToUpdate, update: $updatePayload }) {
id id

View File

@ -2,7 +2,7 @@ import { useMutation } from '@apollo/client';
import { getOperationName } from '@apollo/client/utilities'; import { getOperationName } from '@apollo/client/utilities';
import { import {
UpdateObjectInput, UpdateOneObjectInput,
UpdateOneObjectMetadataItemMutation, UpdateOneObjectMetadataItemMutation,
UpdateOneObjectMetadataItemMutationVariables, UpdateOneObjectMetadataItemMutationVariables,
} from '~/generated-metadata/graphql'; } from '~/generated-metadata/graphql';
@ -27,8 +27,8 @@ export const useUpdateOneObjectMetadataItem = () => {
idToUpdate, idToUpdate,
updatePayload, updatePayload,
}: { }: {
idToUpdate: UpdateOneObjectMetadataItemMutationVariables['idToUpdate']; idToUpdate: UpdateOneObjectInput['id'];
updatePayload: UpdateObjectInput; updatePayload: UpdateOneObjectInput['update'];
}) => { }) => {
return await mutate({ return await mutate({
variables: { variables: {

View File

@ -1,6 +1,6 @@
import { SafeParseSuccess } from 'zod'; import { SafeParseSuccess } from 'zod';
import { UpdateObjectInput } from '~/generated-metadata/graphql'; import { UpdateObjectPayload } from '~/generated-metadata/graphql';
import { settingsUpdateObjectInputSchema } from '../settingsUpdateObjectInputSchema'; import { settingsUpdateObjectInputSchema } from '../settingsUpdateObjectInputSchema';
@ -20,7 +20,7 @@ describe('settingsUpdateObjectInputSchema', () => {
// Then // Then
expect(result.success).toBe(true); expect(result.success).toBe(true);
expect((result as SafeParseSuccess<UpdateObjectInput>).data).toEqual({ expect((result as SafeParseSuccess<UpdateObjectPayload>).data).toEqual({
description: validInput.description, description: validInput.description,
icon: validInput.icon, icon: validInput.icon,
labelIdentifierFieldMetadataId: validInput.labelIdentifierFieldMetadataId, labelIdentifierFieldMetadataId: validInput.labelIdentifierFieldMetadataId,

View File

@ -1,5 +1,5 @@
import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema'; import { objectMetadataItemSchema } from '@/object-metadata/validation-schemas/objectMetadataItemSchema';
import { UpdateObjectInput } from '~/generated-metadata/graphql'; import { UpdateObjectPayload } from '~/generated-metadata/graphql';
import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util'; import { formatMetadataLabelToMetadataNameOrThrows } from '~/pages/settings/data-model/utils/format-metadata-label-to-name.util';
export const settingsUpdateObjectInputSchema = objectMetadataItemSchema export const settingsUpdateObjectInputSchema = objectMetadataItemSchema
@ -13,7 +13,7 @@ export const settingsUpdateObjectInputSchema = objectMetadataItemSchema
labelSingular: true, labelSingular: true,
}) })
.partial() .partial()
.transform<UpdateObjectInput>((value) => ({ .transform<UpdateObjectPayload>((value) => ({
...value, ...value,
nameSingular: value.labelSingular nameSingular: value.labelSingular
? formatMetadataLabelToMetadataNameOrThrows(value.labelSingular) ? formatMetadataLabelToMetadataNameOrThrows(value.labelSingular)

View File

@ -17,6 +17,7 @@ import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-m
import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver'; import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver';
import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto';
import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator'; import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { FieldMetadataService } from './field-metadata.service'; import { FieldMetadataService } from './field-metadata.service';
import { FieldMetadataEntity } from './field-metadata.entity'; import { FieldMetadataEntity } from './field-metadata.entity';
@ -31,6 +32,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
NestjsQueryTypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'), NestjsQueryTypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
WorkspaceMigrationModule, WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceCacheVersionModule,
ObjectMetadataModule, ObjectMetadataModule,
DataSourceModule, DataSourceModule,
TypeORMModule, TypeORMModule,

View File

@ -41,6 +41,7 @@ import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/ut
import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util';
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { import {
FieldMetadataEntity, FieldMetadataEntity,
@ -64,6 +65,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly typeORMService: TypeORMService, private readonly typeORMService: TypeORMService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) { ) {
super(fieldMetadataRepository); super(fieldMetadataRepository);
} }
@ -232,6 +234,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
throw error; throw error;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await this.workspaceCacheVersionService.incrementVersion(
fieldMetadataInput.workspaceId,
);
} }
} }
@ -358,6 +363,9 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
throw error; throw error;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await this.workspaceCacheVersionService.incrementVersion(
fieldMetadataInput.workspaceId,
);
} }
} }
@ -429,6 +437,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
throw error; throw error;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
} }
} }
@ -466,6 +475,7 @@ export class FieldMetadataService extends TypeOrmQueryService<FieldMetadataEntit
public async deleteFieldsMetadata(workspaceId: string) { public async deleteFieldsMetadata(workspaceId: string) {
await this.fieldMetadataRepository.delete({ workspaceId }); await this.fieldMetadataRepository.delete({ workspaceId });
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
} }
private buildUpdatableStandardFieldInput( private buildUpdatableStandardFieldInput(

View File

@ -1,14 +1,20 @@
import { Field, InputType } from '@nestjs/graphql'; import { Field, InputType } from '@nestjs/graphql';
import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql'; import { BeforeUpdateOne } from '@ptc-org/nestjs-query-graphql';
import { IsBoolean, IsOptional, IsString, IsUUID } from 'class-validator'; import {
IsBoolean,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
} from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator'; import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook';
@InputType() @InputType()
@BeforeUpdateOne(BeforeUpdateOneObject) export class UpdateObjectPayload {
export class UpdateObjectInput {
@IsString() @IsString()
@IsOptional() @IsOptional()
@Field({ nullable: true }) @Field({ nullable: true })
@ -56,3 +62,17 @@ export class UpdateObjectInput {
@Field({ nullable: true }) @Field({ nullable: true })
imageIdentifierFieldMetadataId?: string; imageIdentifierFieldMetadataId?: string;
} }
@InputType()
@BeforeUpdateOne(BeforeUpdateOneObject)
export class UpdateOneObjectInput {
@Field(() => UpdateObjectPayload)
update: UpdateObjectPayload;
@IsNotEmpty()
@Field(() => UUIDScalarType, {
description: 'The id of the object to update',
})
@IsUUID()
id!: string;
}

View File

@ -12,12 +12,12 @@ import {
import { Equal, In, Repository } from 'typeorm'; import { Equal, In, Repository } from 'typeorm';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { UpdateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@Injectable() @Injectable()
export class BeforeUpdateOneObject<T extends UpdateObjectInput> export class BeforeUpdateOneObject<T extends UpdateObjectPayload>
implements BeforeUpdateOneHook<T, any> implements BeforeUpdateOneHook<T, any>
{ {
constructor( constructor(
@ -108,7 +108,7 @@ export class BeforeUpdateOneObject<T extends UpdateObjectInput>
// This is temporary until we properly use the MigrationRunner to update column names // This is temporary until we properly use the MigrationRunner to update column names
private checkIfFieldIsEditable( private checkIfFieldIsEditable(
update: UpdateObjectInput, update: UpdateObjectPayload,
objectMetadata: ObjectMetadataEntity, objectMetadata: ObjectMetadataEntity,
) { ) {
if ( if (

View File

@ -18,12 +18,13 @@ import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-met
import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver'; import { ObjectMetadataResolver } from 'src/engine/metadata-modules/object-metadata/object-metadata.resolver';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { ObjectMetadataService } from './object-metadata.service'; import { ObjectMetadataService } from './object-metadata.service';
import { ObjectMetadataEntity } from './object-metadata.entity'; import { ObjectMetadataEntity } from './object-metadata.entity';
import { CreateObjectInput } from './dtos/create-object.input'; import { CreateObjectInput } from './dtos/create-object.input';
import { UpdateObjectInput } from './dtos/update-object.input'; import { UpdateObjectPayload } from './dtos/update-object.input';
import { ObjectMetadataDTO } from './dtos/object-metadata.dto'; import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
@Module({ @Module({
@ -39,6 +40,7 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
DataSourceModule, DataSourceModule,
WorkspaceMigrationModule, WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceCacheVersionModule,
FeatureFlagModule, FeatureFlagModule,
], ],
services: [ObjectMetadataService], services: [ObjectMetadataService],
@ -47,7 +49,7 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
EntityClass: ObjectMetadataEntity, EntityClass: ObjectMetadataEntity,
DTOClass: ObjectMetadataDTO, DTOClass: ObjectMetadataDTO,
CreateDTOClass: CreateObjectInput, CreateDTOClass: CreateObjectInput,
UpdateDTOClass: UpdateObjectInput, UpdateDTOClass: UpdateObjectPayload,
ServiceClass: ObjectMetadataService, ServiceClass: ObjectMetadataService,
pagingStrategy: PagingStrategies.CURSOR, pagingStrategy: PagingStrategies.CURSOR,
read: { read: {
@ -56,9 +58,7 @@ import { ObjectMetadataDTO } from './dtos/object-metadata.dto';
create: { create: {
many: { disabled: true }, many: { disabled: true },
}, },
update: { update: { disabled: true },
many: { disabled: true },
},
delete: { disabled: true }, delete: { disabled: true },
guards: [JwtAuthGuard], guards: [JwtAuthGuard],
}, },

View File

@ -7,6 +7,7 @@ import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto';
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input'; import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service'; import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
@UseGuards(JwtAuthGuard) @UseGuards(JwtAuthGuard)
@Resolver(() => ObjectMetadataDTO) @Resolver(() => ObjectMetadataDTO)
@ -20,4 +21,12 @@ export class ObjectMetadataResolver {
) { ) {
return this.objectMetadataService.deleteOneObject(input, workspaceId); return this.objectMetadataService.deleteOneObject(input, workspaceId);
} }
@Mutation(() => ObjectMetadataDTO)
updateOneObject(
@Args('input') input: UpdateOneObjectInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
return this.objectMetadataService.updateOneObject(input, workspaceId);
}
} }

View File

@ -60,6 +60,8 @@ import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/ut
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity'; import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; import { validateObjectMetadataInput } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util'; import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server/remote-table/utils/udt-name-mapper.util';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import { ObjectMetadataEntity } from './object-metadata.entity'; import { ObjectMetadataEntity } from './object-metadata.entity';
@ -81,6 +83,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private readonly typeORMService: TypeORMService, private readonly typeORMService: TypeORMService,
private readonly workspaceMigrationService: WorkspaceMigrationService, private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) { ) {
super(objectMetadataRepository); super(objectMetadataRepository);
} }
@ -225,6 +228,8 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
workspaceId, workspaceId,
); );
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
return objectMetadata; return objectMetadata;
} }
@ -399,9 +404,24 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
); );
}); });
await this.workspaceCacheVersionService.incrementVersion(
objectMetadataInput.workspaceId,
);
return createdObjectMetadata; return createdObjectMetadata;
} }
public async updateOneObject(
input: UpdateOneObjectInput,
workspaceId: string,
): Promise<ObjectMetadataEntity> {
const updatedObject = await super.updateOne(input.id, input.update);
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
return updatedObject;
}
public async findOneWithinWorkspace( public async findOneWithinWorkspace(
workspaceId: string, workspaceId: string,
options: FindOneOptions<ObjectMetadataEntity>, options: FindOneOptions<ObjectMetadataEntity>,
@ -475,6 +495,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
public async deleteObjectsMetadata(workspaceId: string) { public async deleteObjectsMetadata(workspaceId: string) {
await this.objectMetadataRepository.delete({ workspaceId }); await this.objectMetadataRepository.delete({ workspaceId });
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
} }
private async createObjectRelationsMetadataAndMigrations( private async createObjectRelationsMetadataAndMigrations(

View File

@ -2,11 +2,11 @@ import { BadRequestException } from '@nestjs/common';
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input';
import { UpdateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input';
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
export const validateObjectMetadataInput = < export const validateObjectMetadataInput = <
T extends UpdateObjectInput | CreateObjectInput, T extends UpdateObjectPayload | CreateObjectInput,
>( >(
objectMetadataInput: T, objectMetadataInput: T,
): void => { ): void => {

View File

@ -0,0 +1,13 @@
import { InputType } from '@nestjs/graphql';
import { IDField } from '@ptc-org/nestjs-query-graphql';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@InputType()
export class DeleteOneRelationInput {
@IDField(() => UUIDScalarType, {
description: 'The id of the relation to delete.',
})
id!: string;
}

View File

@ -11,6 +11,8 @@ import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
import { RelationMetadataResolver } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.resolver';
import { RelationMetadataService } from './relation-metadata.service'; import { RelationMetadataService } from './relation-metadata.service';
import { RelationMetadataEntity } from './relation-metadata.entity'; import { RelationMetadataEntity } from './relation-metadata.entity';
@ -30,6 +32,7 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
FieldMetadataModule, FieldMetadataModule,
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceMigrationModule, WorkspaceMigrationModule,
WorkspaceCacheVersionModule,
], ],
services: [RelationMetadataService], services: [RelationMetadataService],
resolvers: [ resolvers: [
@ -41,13 +44,13 @@ import { RelationMetadataDTO } from './dtos/relation-metadata.dto';
pagingStrategy: PagingStrategies.CURSOR, pagingStrategy: PagingStrategies.CURSOR,
create: { many: { disabled: true } }, create: { many: { disabled: true } },
update: { disabled: true }, update: { disabled: true },
delete: { many: { disabled: true } }, delete: { disabled: true },
guards: [JwtAuthGuard], guards: [JwtAuthGuard],
}, },
], ],
}), }),
], ],
providers: [RelationMetadataService], providers: [RelationMetadataService, RelationMetadataResolver],
exports: [RelationMetadataService], exports: [RelationMetadataService],
}) })
export class RelationMetadataModule {} export class RelationMetadataModule {}

View File

@ -0,0 +1,28 @@
import { UseGuards } from '@nestjs/common';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { JwtAuthGuard } from 'src/engine/guards/jwt.auth.guard';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto';
import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input';
@UseGuards(JwtAuthGuard)
@Resolver()
export class RelationMetadataResolver {
constructor(
private readonly relationMetadataService: RelationMetadataService,
) {}
@Mutation(() => RelationMetadataDTO)
deleteOneRelation(
@Args('input') input: DeleteOneRelationInput,
@AuthWorkspace() { id: workspaceId }: Workspace,
) {
return this.relationMetadataService.deleteOneRelation(
input.id,
workspaceId,
);
}
}

View File

@ -26,6 +26,7 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException';
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { import {
RelationMetadataEntity, RelationMetadataEntity,
@ -42,6 +43,7 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
private readonly fieldMetadataService: FieldMetadataService, private readonly fieldMetadataService: FieldMetadataService,
private readonly workspaceMigrationService: WorkspaceMigrationService, private readonly workspaceMigrationService: WorkspaceMigrationService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) { ) {
super(relationMetadataRepository); super(relationMetadataRepository);
} }
@ -110,6 +112,10 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
relationMetadataInput.workspaceId, relationMetadataInput.workspaceId,
); );
await this.workspaceCacheVersionService.incrementVersion(
relationMetadataInput.workspaceId,
);
return createdRelationMetadata; return createdRelationMetadata;
} }
@ -307,7 +313,10 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
}); });
} }
override async deleteOne(id: string): Promise<RelationMetadataEntity> { public async deleteOneRelation(
id: string,
workspaceId: string,
): Promise<RelationMetadataEntity> {
// TODO: This logic is duplicated with the BeforeDeleteOneRelation hook // TODO: This logic is duplicated with the BeforeDeleteOneRelation hook
const relationMetadata = await this.relationMetadataRepository.findOne({ const relationMetadata = await this.relationMetadataRepository.findOne({
where: { id }, where: { id },
@ -318,10 +327,10 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
throw new NotFoundException('Relation does not exist'); throw new NotFoundException('Relation does not exist');
} }
const deletedRelationMetadata = super.deleteOne(id); await super.deleteOne(id);
// TODO: Move to a cdc scheduler // TODO: Move to a cdc scheduler
this.fieldMetadataService.deleteMany({ await this.fieldMetadataService.deleteMany({
id: { id: {
in: [ in: [
relationMetadata.fromFieldMetadataId, relationMetadata.fromFieldMetadataId,
@ -330,7 +339,10 @@ export class RelationMetadataService extends TypeOrmQueryService<RelationMetadat
}, },
}); });
return deletedRelationMetadata; await this.workspaceCacheVersionService.incrementVersion(workspaceId);
// TODO: Return id for delete endpoints
return relationMetadata;
} }
async findManyRelationMetadataByFieldMetadataIds( async findManyRelationMetadataByFieldMetadataIds(

View File

@ -100,9 +100,6 @@ export class WorkspaceMigrationRunnerService {
); );
} }
// Increment workspace cache version
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
return flattenedPendingMigrations; return flattenedPendingMigrations;
} }

View File

@ -15,6 +15,7 @@ import { WorkspaceSyncObjectMetadataService } from 'src/engine/workspace-manager
import { WorkspaceSyncRelationMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service'; import { WorkspaceSyncRelationMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-relation-metadata.service';
import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service'; import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service';
import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/workspace-migration-builder/workspace-migration-builder.module'; import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/workspace-migration-builder/workspace-migration-builder.module';
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
@Module({ @Module({
imports: [ imports: [
@ -30,6 +31,7 @@ import { WorkspaceMigrationBuilderModule } from 'src/engine/workspace-manager/wo
'metadata', 'metadata',
), ),
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'), TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
WorkspaceCacheVersionModule,
], ],
providers: [ providers: [
...workspaceSyncMetadataFactories, ...workspaceSyncMetadataFactories,

View File

@ -12,6 +12,7 @@ import { WorkspaceSyncRelationMetadataService } from 'src/engine/workspace-manag
import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service'; import { WorkspaceSyncFieldMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-field-metadata.service';
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage'; import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
interface SynchronizeOptions { interface SynchronizeOptions {
applyChanges?: boolean; applyChanges?: boolean;
@ -29,6 +30,7 @@ export class WorkspaceSyncMetadataService {
private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService, private readonly workspaceSyncObjectMetadataService: WorkspaceSyncObjectMetadataService,
private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService, private readonly workspaceSyncRelationMetadataService: WorkspaceSyncRelationMetadataService,
private readonly workspaceSyncFieldMetadataService: WorkspaceSyncFieldMetadataService, private readonly workspaceSyncFieldMetadataService: WorkspaceSyncFieldMetadataService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
) {} ) {}
/** /**
@ -127,6 +129,9 @@ export class WorkspaceSyncMetadataService {
await queryRunner.rollbackTransaction(); await queryRunner.rollbackTransaction();
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
await this.workspaceCacheVersionService.incrementVersion(
context.workspaceId,
);
} }
return { return {