feat: soft delete (#6576)
Implement soft delete on standards and custom objects. This is a temporary solution, when we drop `pg_graphql` we should rely on the `softDelete` functions of TypeORM. --------- Co-authored-by: Félix Malfait <felix.malfait@gmail.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
This commit is contained in:
@ -3,6 +3,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { stringifyWithoutKeyQuote } from 'src/engine/api/graphql/workspace-query-builder/utils/stringify-without-key-quote.util';
|
||||
import { isDefined } from 'src/utils/is-defined';
|
||||
|
||||
import { ArgsAliasFactory } from './args-alias.factory';
|
||||
|
||||
@ -13,10 +14,18 @@ export class ArgsStringFactory {
|
||||
create(
|
||||
initialArgs: Record<string, any> | undefined,
|
||||
fieldMetadataCollection: FieldMetadataInterface[],
|
||||
softDeletable?: boolean,
|
||||
): string | null {
|
||||
if (!initialArgs) {
|
||||
return null;
|
||||
}
|
||||
if (softDeletable) {
|
||||
initialArgs.filter = {
|
||||
and: [initialArgs.filter, { deletedAt: { is: 'NULL' } }].filter(
|
||||
isDefined,
|
||||
),
|
||||
};
|
||||
}
|
||||
let argsString = '';
|
||||
const computedArgs = this.argsAliasFactory.create(
|
||||
initialArgs,
|
||||
|
||||
@ -22,19 +22,23 @@ export class FieldsStringFactory {
|
||||
private readonly relationFieldAliasFactory: RelationFieldAliasFactory,
|
||||
) {}
|
||||
|
||||
create(
|
||||
async create(
|
||||
info: GraphQLResolveInfo,
|
||||
fieldMetadataCollection: FieldMetadataInterface[],
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
withSoftDeleted?: boolean,
|
||||
): Promise<string> {
|
||||
const selectedFields: Partial<Record> = graphqlFields(info);
|
||||
|
||||
return this.createFieldsStringRecursive(
|
||||
const res = await this.createFieldsStringRecursive(
|
||||
info,
|
||||
selectedFields,
|
||||
fieldMetadataCollection,
|
||||
objectMetadataCollection,
|
||||
withSoftDeleted ?? false,
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async createFieldsStringRecursive(
|
||||
@ -42,6 +46,7 @@ export class FieldsStringFactory {
|
||||
selectedFields: Partial<Record>,
|
||||
fieldMetadataCollection: FieldMetadataInterface[],
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
withSoftDeleted: boolean,
|
||||
accumulator = '',
|
||||
): Promise<string> {
|
||||
const fieldMetadataMap = new Map(
|
||||
@ -65,6 +70,7 @@ export class FieldsStringFactory {
|
||||
fieldMetadata,
|
||||
objectMetadataCollection,
|
||||
info,
|
||||
withSoftDeleted,
|
||||
);
|
||||
|
||||
fieldAlias = alias;
|
||||
@ -91,6 +97,7 @@ export class FieldsStringFactory {
|
||||
fieldValue,
|
||||
fieldMetadataCollection,
|
||||
objectMetadataCollection,
|
||||
withSoftDeleted,
|
||||
accumulator,
|
||||
);
|
||||
accumulator += `}\n`;
|
||||
|
||||
@ -36,6 +36,7 @@ export class FindManyQueryFactory {
|
||||
const argsString = this.argsStringFactory.create(
|
||||
args,
|
||||
options.fieldMetadataCollection,
|
||||
!options.withSoftDeleted && !!options.objectMetadataItem.isSoftDeletable,
|
||||
);
|
||||
|
||||
return `
|
||||
|
||||
@ -26,10 +26,12 @@ export class FindOneQueryFactory {
|
||||
options.info,
|
||||
options.fieldMetadataCollection,
|
||||
options.objectMetadataCollection,
|
||||
options.withSoftDeleted,
|
||||
);
|
||||
const argsString = this.argsStringFactory.create(
|
||||
args,
|
||||
options.fieldMetadataCollection,
|
||||
!options.withSoftDeleted && !!options.objectMetadataItem.isSoftDeletable,
|
||||
);
|
||||
|
||||
return `
|
||||
|
||||
@ -12,7 +12,6 @@ import {
|
||||
RelationDirection,
|
||||
} from 'src/engine/utils/deduce-relation-direction.util';
|
||||
import { getFieldArgumentsByKey } from 'src/engine/api/graphql/workspace-query-builder/utils/get-field-arguments-by-key.util';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
||||
|
||||
@ -27,7 +26,6 @@ export class RelationFieldAliasFactory {
|
||||
@Inject(forwardRef(() => FieldsStringFactory))
|
||||
private readonly fieldsStringFactory: CircularDep<FieldsStringFactory>,
|
||||
private readonly argsStringFactory: ArgsStringFactory,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
) {}
|
||||
|
||||
create(
|
||||
@ -36,6 +34,7 @@ export class RelationFieldAliasFactory {
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
info: GraphQLResolveInfo,
|
||||
withSoftDeleted?: boolean,
|
||||
): Promise<string> {
|
||||
if (!isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||
throw new Error(`Field ${fieldMetadata.name} is not a relation field`);
|
||||
@ -47,6 +46,7 @@ export class RelationFieldAliasFactory {
|
||||
fieldMetadata,
|
||||
objectMetadataCollection,
|
||||
info,
|
||||
withSoftDeleted,
|
||||
);
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ export class RelationFieldAliasFactory {
|
||||
fieldMetadata: FieldMetadataInterface,
|
||||
objectMetadataCollection: ObjectMetadataInterface[],
|
||||
info: GraphQLResolveInfo,
|
||||
withSoftDeleted?: boolean,
|
||||
): Promise<string> {
|
||||
const relationMetadata =
|
||||
fieldMetadata.fromRelationMetadata ?? fieldMetadata.toRelationMetadata;
|
||||
@ -98,9 +99,11 @@ export class RelationFieldAliasFactory {
|
||||
relationDirection === RelationDirection.FROM
|
||||
) {
|
||||
const args = getFieldArgumentsByKey(info, fieldKey);
|
||||
|
||||
const argsString = this.argsStringFactory.create(
|
||||
args,
|
||||
referencedObjectMetadata.fields ?? [],
|
||||
!withSoftDeleted && !!referencedObjectMetadata.isSoftDeletable,
|
||||
);
|
||||
const fieldsString =
|
||||
await this.fieldsStringFactory.createFieldsStringRecursive(
|
||||
@ -108,6 +111,7 @@ export class RelationFieldAliasFactory {
|
||||
fieldValue,
|
||||
referencedObjectMetadata.fields ?? [],
|
||||
objectMetadataCollection,
|
||||
withSoftDeleted ?? false,
|
||||
);
|
||||
|
||||
return `
|
||||
@ -137,6 +141,7 @@ export class RelationFieldAliasFactory {
|
||||
fieldValue,
|
||||
referencedObjectMetadata.fields ?? [],
|
||||
objectMetadataCollection,
|
||||
withSoftDeleted ?? false,
|
||||
);
|
||||
|
||||
// Otherwise it means it's a relation destination is of kind ONE
|
||||
|
||||
@ -3,6 +3,7 @@ export interface Record {
|
||||
[key: string]: any;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
deletedAt: string | null;
|
||||
}
|
||||
|
||||
export type RecordFilter = {
|
||||
|
||||
@ -8,4 +8,5 @@ export interface WorkspaceQueryBuilderOptions {
|
||||
info: GraphQLResolveInfo;
|
||||
fieldMetadataCollection: FieldMetadataInterface[];
|
||||
objectMetadataCollection: ObjectMetadataInterface[];
|
||||
withSoftDeleted?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user