From 038b38879f872776adebd8345edc09bb9697809a Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 28 Sep 2022 18:32:03 +0200 Subject: [PATCH 1/6] chore(backend): add soft delete migration --- .../20220928162525_soft-delete-base-model.ts | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 packages/backend/src/db/migrations/20220928162525_soft-delete-base-model.ts diff --git a/packages/backend/src/db/migrations/20220928162525_soft-delete-base-model.ts b/packages/backend/src/db/migrations/20220928162525_soft-delete-base-model.ts new file mode 100644 index 00000000..862b6691 --- /dev/null +++ b/packages/backend/src/db/migrations/20220928162525_soft-delete-base-model.ts @@ -0,0 +1,31 @@ +import { Knex } from "knex"; + +async function addDeletedColumn(knex: Knex, tableName: string) { + return await knex.schema.table(tableName, (table) => { + table.timestamp('deleted_at').nullable(); + }); +} + +async function dropDeletedColumn(knex: Knex, tableName: string) { + return await knex.schema.table(tableName, (table) => { + table.dropColumn('deleted_at'); + }); +} + +export async function up(knex: Knex): Promise { + await addDeletedColumn(knex, 'steps'); + await addDeletedColumn(knex, 'flows'); + await addDeletedColumn(knex, 'executions'); + await addDeletedColumn(knex, 'execution_steps'); + await addDeletedColumn(knex, 'users'); + await addDeletedColumn(knex, 'connections'); +} + +export async function down(knex: Knex): Promise { + await dropDeletedColumn(knex, 'steps'); + await dropDeletedColumn(knex, 'flows'); + await dropDeletedColumn(knex, 'executions'); + await dropDeletedColumn(knex, 'execution_steps'); + await dropDeletedColumn(knex, 'users'); + await dropDeletedColumn(knex, 'connections'); +} From 8be352765e77151d248f570f7711883327290b16 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Wed, 28 Sep 2022 21:39:21 +0200 Subject: [PATCH 2/6] feat: soft delete database entities --- packages/backend/src/models/base.ts | 10 ++++- packages/backend/src/models/query-builder.ts | 43 ++++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 packages/backend/src/models/query-builder.ts diff --git a/packages/backend/src/models/base.ts b/packages/backend/src/models/base.ts index 95c40746..6bfdc60d 100644 --- a/packages/backend/src/models/base.ts +++ b/packages/backend/src/models/base.ts @@ -2,9 +2,15 @@ import { AjvValidator, Model, snakeCaseMappers } from 'objection'; import type { QueryContext, ModelOptions, ColumnNameMappers } from 'objection'; import addFormats from 'ajv-formats'; +import ExtendedQueryBuilder from './query-builder'; + class Base extends Model { createdAt!: string; updatedAt!: string; + deletedAt: string; + + QueryBuilderType!: ExtendedQueryBuilder; + static QueryBuilder = ExtendedQueryBuilder; static get columnNameMappers(): ColumnNameMappers { return snakeCaseMappers(); @@ -30,10 +36,10 @@ class Base extends Model { this.updatedAt = new Date().toISOString(); } - async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise { + async $beforeUpdate(opts: ModelOptions, queryContext: QueryContext): Promise { this.updatedAt = new Date().toISOString(); - await super.$beforeUpdate(opt, queryContext); + await super.$beforeUpdate(opts, queryContext); } } diff --git a/packages/backend/src/models/query-builder.ts b/packages/backend/src/models/query-builder.ts new file mode 100644 index 00000000..9e89f4bf --- /dev/null +++ b/packages/backend/src/models/query-builder.ts @@ -0,0 +1,43 @@ +import { Model, Page, PartialModelObject, ForClassMethod, AnyQueryBuilder } from "objection"; + +const DELETED_COLUMN_NAME = 'deleted_at'; + +const buildQueryBuidlerForClass = (): ForClassMethod => { + return (modelClass) => { + const qb: AnyQueryBuilder = Model.QueryBuilder.forClass.call(ExtendedQueryBuilder, modelClass); + qb.onBuild((builder) => { + if (!builder.context().withSoftDeleted) { + builder.whereNull(`${qb.modelClass().tableName}.${DELETED_COLUMN_NAME}`); + } + }); + return qb; + }; +}; + +class ExtendedQueryBuilder extends Model.QueryBuilder { + ArrayQueryBuilderType!: ExtendedQueryBuilder; + SingleQueryBuilderType!: ExtendedQueryBuilder; + MaybeSingleQueryBuilderType!: ExtendedQueryBuilder; + NumberQueryBuilderType!: ExtendedQueryBuilder; + PageQueryBuilderType!: ExtendedQueryBuilder>; + + static forClass: ForClassMethod = buildQueryBuidlerForClass(); + + delete() { + return this.patch({ + [DELETED_COLUMN_NAME]: (new Date()).toISOString(), + } as unknown as PartialModelObject); + } + + hardDelete() { + return super.delete(); + } + + restore() { + return this.patch({ + [DELETED_COLUMN_NAME]: null, + } as unknown as PartialModelObject); + } +} + +export default ExtendedQueryBuilder; From 816d63676d722ffc4527b5db9dbfb0ddeff6275b Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 29 Sep 2022 11:18:03 +0200 Subject: [PATCH 3/6] feat: add withSoftDeleted functionality in base model --- packages/backend/src/models/query-builder.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/backend/src/models/query-builder.ts b/packages/backend/src/models/query-builder.ts index 9e89f4bf..8d960068 100644 --- a/packages/backend/src/models/query-builder.ts +++ b/packages/backend/src/models/query-builder.ts @@ -33,6 +33,11 @@ class ExtendedQueryBuilder extends Model.QueryBuilder< return super.delete(); } + withSoftDeleted() { + this.context().withSoftDeleted = true; + return this; + } + restore() { return this.patch({ [DELETED_COLUMN_NAME]: null, From 6a438a34f00a441e12ab971af2d14843d1dbed62 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 29 Sep 2022 19:53:45 +0200 Subject: [PATCH 4/6] chore: update query param type in pagination helper --- packages/backend/src/helpers/pagination.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/helpers/pagination.ts b/packages/backend/src/helpers/pagination.ts index b283dd93..eb0a760a 100644 --- a/packages/backend/src/helpers/pagination.ts +++ b/packages/backend/src/helpers/pagination.ts @@ -1,7 +1,8 @@ -import { QueryBuilder, Model } from 'objection'; +import { Model } from 'objection'; +import ExtendedQueryBuilder from '../models/query-builder'; const paginate = async ( - query: QueryBuilder, + query: ExtendedQueryBuilder, limit: number, offset: number ) => { From 14046505666d431d2e643f56a2377c8dee3fefc4 Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 29 Sep 2022 19:54:08 +0200 Subject: [PATCH 5/6] chore: update querybuilder type in flow model --- packages/backend/src/models/flow.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/models/flow.ts b/packages/backend/src/models/flow.ts index 388c477c..a9ff261b 100644 --- a/packages/backend/src/models/flow.ts +++ b/packages/backend/src/models/flow.ts @@ -1,5 +1,6 @@ import { ValidationError } from 'objection'; -import type { ModelOptions, QueryContext, QueryBuilder } from 'objection'; +import type { ModelOptions, QueryContext } from 'objection'; +import ExtendedQueryBuilder from './query-builder'; import Base from './base'; import Step from './step'; import Execution from './execution'; @@ -35,7 +36,7 @@ class Flow extends Base { from: 'flows.id', to: 'steps.flow_id', }, - filter(builder: QueryBuilder) { + filter(builder: ExtendedQueryBuilder) { builder.orderBy('position', 'asc'); }, }, From 56c8666f029cd04538cce072be1dbce47c98a16d Mon Sep 17 00:00:00 2001 From: Ali BARIN Date: Thu, 29 Sep 2022 19:54:41 +0200 Subject: [PATCH 6/6] fix: associate executions in User model for types --- packages/backend/src/models/user.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index f753b889..896c4ff9 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -13,6 +13,7 @@ class User extends Base { connections?: [Connection]; flows?: [Flow]; steps?: [Step]; + executions?: [Execution]; static tableName = 'users';