Merge pull request #554 from automatisch/issue-553

feat: soft delete database entities
This commit is contained in:
Ömer Faruk Aydın
2022-10-16 13:43:15 +02:00
committed by GitHub
6 changed files with 94 additions and 6 deletions

View File

@@ -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<void> {
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<void> {
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');
}

View File

@@ -1,7 +1,8 @@
import { QueryBuilder, Model } from 'objection'; import { Model } from 'objection';
import ExtendedQueryBuilder from '../models/query-builder';
const paginate = async ( const paginate = async (
query: QueryBuilder<Model, Model[]>, query: ExtendedQueryBuilder<Model, Model[]>,
limit: number, limit: number,
offset: number offset: number
) => { ) => {

View File

@@ -2,9 +2,15 @@ import { AjvValidator, Model, snakeCaseMappers } from 'objection';
import type { QueryContext, ModelOptions, ColumnNameMappers } from 'objection'; import type { QueryContext, ModelOptions, ColumnNameMappers } from 'objection';
import addFormats from 'ajv-formats'; import addFormats from 'ajv-formats';
import ExtendedQueryBuilder from './query-builder';
class Base extends Model { class Base extends Model {
createdAt!: string; createdAt!: string;
updatedAt!: string; updatedAt!: string;
deletedAt: string;
QueryBuilderType!: ExtendedQueryBuilder<this>;
static QueryBuilder = ExtendedQueryBuilder;
static get columnNameMappers(): ColumnNameMappers { static get columnNameMappers(): ColumnNameMappers {
return snakeCaseMappers(); return snakeCaseMappers();
@@ -30,10 +36,10 @@ class Base extends Model {
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
} }
async $beforeUpdate(opt: ModelOptions, queryContext: QueryContext): Promise<void> { async $beforeUpdate(opts: ModelOptions, queryContext: QueryContext): Promise<void> {
this.updatedAt = new Date().toISOString(); this.updatedAt = new Date().toISOString();
await super.$beforeUpdate(opt, queryContext); await super.$beforeUpdate(opts, queryContext);
} }
} }

View File

@@ -1,5 +1,6 @@
import { ValidationError } from 'objection'; 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 Base from './base';
import Step from './step'; import Step from './step';
import Execution from './execution'; import Execution from './execution';
@@ -36,7 +37,7 @@ class Flow extends Base {
from: 'flows.id', from: 'flows.id',
to: 'steps.flow_id', to: 'steps.flow_id',
}, },
filter(builder: QueryBuilder<Step>) { filter(builder: ExtendedQueryBuilder<Step>) {
builder.orderBy('position', 'asc'); builder.orderBy('position', 'asc');
}, },
}, },

View File

@@ -0,0 +1,48 @@
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<M extends Model, R = M[]> extends Model.QueryBuilder<M, R> {
ArrayQueryBuilderType!: ExtendedQueryBuilder<M, M[]>;
SingleQueryBuilderType!: ExtendedQueryBuilder<M, M>;
MaybeSingleQueryBuilderType!: ExtendedQueryBuilder<M, M | undefined>;
NumberQueryBuilderType!: ExtendedQueryBuilder<M, number>;
PageQueryBuilderType!: ExtendedQueryBuilder<M, Page<M>>;
static forClass: ForClassMethod = buildQueryBuidlerForClass();
delete() {
return this.patch({
[DELETED_COLUMN_NAME]: (new Date()).toISOString(),
} as unknown as PartialModelObject<M>);
}
hardDelete() {
return super.delete();
}
withSoftDeleted() {
this.context().withSoftDeleted = true;
return this;
}
restore() {
return this.patch({
[DELETED_COLUMN_NAME]: null,
} as unknown as PartialModelObject<M>);
}
}
export default ExtendedQueryBuilder;

View File

@@ -13,6 +13,7 @@ class User extends Base {
connections?: Connection[]; connections?: Connection[];
flows?: Flow[]; flows?: Flow[];
steps?: Step[]; steps?: Step[];
executions?: Execution[];
static tableName = 'users'; static tableName = 'users';