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:
@ -1,6 +1,7 @@
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIsPimaryField } from 'src/engine/twenty-orm/decorators/workspace-is-primary-field.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsPrimaryField } from 'src/engine/twenty-orm/decorators/workspace-is-primary-field.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { BASE_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
|
||||
@ -13,7 +14,7 @@ export abstract class BaseWorkspaceEntity {
|
||||
defaultValue: 'uuid',
|
||||
icon: 'Icon123',
|
||||
})
|
||||
@WorkspaceIsPimaryField()
|
||||
@WorkspaceIsPrimaryField()
|
||||
@WorkspaceIsSystem()
|
||||
id: string;
|
||||
|
||||
@ -25,7 +26,7 @@ export abstract class BaseWorkspaceEntity {
|
||||
icon: 'IconCalendar',
|
||||
defaultValue: 'now',
|
||||
})
|
||||
createdAt: Date;
|
||||
createdAt: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
|
||||
@ -35,5 +36,15 @@ export abstract class BaseWorkspaceEntity {
|
||||
icon: 'IconCalendarClock',
|
||||
defaultValue: 'now',
|
||||
})
|
||||
updatedAt: Date;
|
||||
updatedAt: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
label: 'Deleted at',
|
||||
description: 'Date when the record was deleted',
|
||||
icon: 'IconCalendarMinus',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
deletedAt?: string | null;
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import {
|
||||
RelationOnDeleteAction,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceCustomObject } from 'src/engine/twenty-orm/decorators/workspace-custom-object.decorator';
|
||||
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
@ -21,7 +21,9 @@ import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/not
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceCustomObject()
|
||||
@WorkspaceCustomEntity({
|
||||
softDelete: true,
|
||||
})
|
||||
export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
import { metadataArgsStorage } from 'src/engine/twenty-orm/storage/metadata-args.storage';
|
||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||
|
||||
export function WorkspaceCustomObject(): ClassDecorator {
|
||||
interface WorkspaceCustomEntityOptions {
|
||||
softDelete?: boolean;
|
||||
}
|
||||
|
||||
export function WorkspaceCustomEntity(
|
||||
options: WorkspaceCustomEntityOptions = {},
|
||||
): ClassDecorator {
|
||||
return (target) => {
|
||||
const gate = TypedReflect.getMetadata(
|
||||
'workspace:gate-metadata-args',
|
||||
@ -11,6 +17,7 @@ export function WorkspaceCustomObject(): ClassDecorator {
|
||||
metadataArgsStorage.addExtendedEntities({
|
||||
target,
|
||||
gate,
|
||||
softDelete: options.softDelete,
|
||||
});
|
||||
};
|
||||
}
|
||||
@ -12,6 +12,7 @@ interface WorkspaceEntityOptions {
|
||||
icon?: string;
|
||||
labelIdentifierStandardId?: string;
|
||||
imageIdentifierStandardId?: string;
|
||||
softDelete?: boolean;
|
||||
}
|
||||
|
||||
export function WorkspaceEntity(
|
||||
@ -47,6 +48,7 @@ export function WorkspaceEntity(
|
||||
isAuditLogged,
|
||||
isSystem,
|
||||
gate,
|
||||
softDelete: options.softDelete,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { TypedReflect } from 'src/utils/typed-reflect';
|
||||
|
||||
export function WorkspaceIsPimaryField(): PropertyDecorator {
|
||||
export function WorkspaceIsPrimaryField(): PropertyDecorator {
|
||||
return (object, propertyKey) => {
|
||||
TypedReflect.defineMetadata(
|
||||
'workspace:is-primary-field-metadata-args',
|
||||
|
||||
@ -22,12 +22,18 @@ type EntitySchemaColumnMap = {
|
||||
export class EntitySchemaColumnFactory {
|
||||
create(
|
||||
fieldMetadataCollection: FieldMetadataEntity[],
|
||||
softDelete: boolean,
|
||||
): EntitySchemaColumnMap {
|
||||
let entitySchemaColumnMap: EntitySchemaColumnMap = {};
|
||||
|
||||
for (const fieldMetadata of fieldMetadataCollection) {
|
||||
const key = fieldMetadata.name;
|
||||
|
||||
// Skip deletedAt column if soft delete is not enabled
|
||||
if (!softDelete && key === 'deletedAt') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isRelationFieldMetadataType(fieldMetadata.type)) {
|
||||
const relationMetadata =
|
||||
fieldMetadata.fromRelationMetadata ??
|
||||
|
||||
@ -21,6 +21,7 @@ export class EntitySchemaFactory {
|
||||
): Promise<EntitySchema> {
|
||||
const columns = this.entitySchemaColumnFactory.create(
|
||||
objectMetadata.fields,
|
||||
objectMetadata.isSoftDeletable ?? false,
|
||||
);
|
||||
|
||||
const relations = await this.entitySchemaRelationFactory.create(
|
||||
|
||||
@ -54,12 +54,15 @@ export interface WorkspaceEntityMetadataArgs {
|
||||
/**
|
||||
* Label identifier.
|
||||
*/
|
||||
|
||||
readonly labelIdentifierStandardId: string | null;
|
||||
|
||||
/**
|
||||
* Image identifier.
|
||||
*/
|
||||
|
||||
readonly imageIdentifierStandardId: string | null;
|
||||
|
||||
/**
|
||||
* Enable soft delete.
|
||||
*/
|
||||
readonly softDelete?: boolean;
|
||||
}
|
||||
|
||||
@ -13,4 +13,9 @@ export interface WorkspaceExtendedEntityMetadataArgs {
|
||||
* Entity gate.
|
||||
*/
|
||||
readonly gate?: Gate;
|
||||
|
||||
/**
|
||||
* Enable soft delete.
|
||||
*/
|
||||
readonly softDelete?: boolean;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user