Skip to content

Commit 28de1cd

Browse files
Samarpan  BhattacharyaSamarpan  Bhattacharya
Samarpan Bhattacharya
authored and
Samarpan Bhattacharya
committed
feat(repository): allow deletedBy id to be configurable using class protected property
BREAKING CHANGE: change approach of deletedById key provider gh-99
1 parent 1be1dc4 commit 28de1cd

7 files changed

+477
-40
lines changed

README.md

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ export class ItemRepository extends SoftCrudRepositoryMixin<
193193

194194
### deletedBy
195195

196-
Whenever any entry is deleted using deleteById, delete and deleteAll repository methods, it also sets deletedBy column with a value with user id whoever is logged in currently. Hence it uses a Getter function of IAuthUser type. However, if you want to use some other attribute of user model other than id, you can do it by sending extra options to repository methods - deleteById, delete and deleteAll. Here is an example.
196+
Whenever any entry is deleted using deleteById, delete and deleteAll repository methods, it also sets deletedBy column with a value with user id whoever is logged in currently. Hence it uses a Getter function of IAuthUser type. However, if you want to use some other attribute of user model other than id, you can do it by overriding deletedByIdKey. Here is an example.
197197

198198
```ts
199199
import {Getter, inject} from '@loopback/core';
@@ -212,27 +212,10 @@ export class UserRepository extends SoftCrudRepository<
212212
@inject('datasources.pgdb') dataSource: PgdbDataSource,
213213
@inject.getter(AuthenticationBindings.CURRENT_USER, {optional: true})
214214
protected readonly getCurrentUser: Getter<IAuthUser | undefined>,
215+
protected readonly deletedByIdKey: string = 'userTenantId',
215216
) {
216217
super(User, dataSource, getCurrentUser);
217218
}
218-
219-
async delete(entity: User, options?: Options): Promise<void> {
220-
return super.delete(entity, {
221-
userIdentifierKey: 'userTenantId',
222-
});
223-
}
224-
225-
async deleteAll(where?: Where<User>, options?: Options): Promise<Count> {
226-
return super.deleteAll(where, {
227-
userIdentifierKey: 'userTenantId',
228-
});
229-
}
230-
231-
async deleteById(id: string, options?: Options): Promise<void> {
232-
return super.deleteById(id, {
233-
userIdentifierKey: 'userTenantId',
234-
});
235-
}
236219
}
237220
```
238221

src/__tests__/unit/mixin/soft-crud.mixin.unit.ts

Lines changed: 157 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {expect} from '@loopback/testlab';
77

88
import {Constructor, Getter} from '@loopback/context';
99
import {
10+
DefaultCrudRepository,
1011
DefaultTransactionalRepository,
1112
Entity,
1213
EntityNotFoundError,
@@ -32,6 +33,16 @@ class Customer extends SoftDeleteEntity {
3233
email: string;
3334
}
3435

36+
@model()
37+
class Customer2 extends SoftDeleteEntity {
38+
@property({
39+
id: true,
40+
})
41+
id: number;
42+
@property()
43+
email: string;
44+
}
45+
3546
class CustomerCrudRepo extends SoftCrudRepositoryMixin<
3647
Customer,
3748
typeof Customer.prototype.id,
@@ -51,19 +62,45 @@ class CustomerCrudRepo extends SoftCrudRepositoryMixin<
5162
}
5263
}
5364

65+
class Customer2CrudRepo extends SoftCrudRepositoryMixin<
66+
Customer,
67+
typeof Customer.prototype.id,
68+
Constructor<
69+
DefaultCrudRepository<Customer, typeof Customer.prototype.id, {}>
70+
>,
71+
{}
72+
>(DefaultCrudRepository) {
73+
constructor(
74+
entityClass: typeof Entity & {
75+
prototype: Customer;
76+
},
77+
dataSource: juggler.DataSource,
78+
readonly getCurrentUser: Getter<IAuthUser | undefined>,
79+
readonly deletedByIdKey: string = 'id',
80+
) {
81+
super(entityClass, dataSource, getCurrentUser);
82+
}
83+
}
84+
5485
describe('SoftCrudRepositoryMixin', () => {
5586
let repo: CustomerCrudRepo;
87+
let repoWithCustomDeletedByKey: Customer2CrudRepo;
88+
const userData = {
89+
id: '1',
90+
username: 'test',
91+
};
5692

5793
before(() => {
5894
const ds: juggler.DataSource = new juggler.DataSource({
5995
name: 'db',
6096
connector: 'memory',
6197
});
62-
repo = new CustomerCrudRepo(Customer, ds, () =>
63-
Promise.resolve({
64-
id: '1',
65-
username: 'test',
66-
}),
98+
repo = new CustomerCrudRepo(Customer, ds, () => Promise.resolve(userData));
99+
repoWithCustomDeletedByKey = new Customer2CrudRepo(
100+
Customer2,
101+
ds,
102+
() => Promise.resolve(userData),
103+
'username',
67104
);
68105
});
69106

@@ -504,6 +541,56 @@ describe('SoftCrudRepositoryMixin', () => {
504541
});
505542
});
506543

544+
describe('deleteById', () => {
545+
beforeEach(setupTestData);
546+
afterEach(clearTestData);
547+
548+
it('should soft delete entries', async () => {
549+
await repo.deleteById(1);
550+
try {
551+
await repo.findById(1);
552+
fail();
553+
} catch (e) {
554+
expect(e.message).to.be.equal('EntityNotFound');
555+
}
556+
const afterDeleteIncludeSoftDeleted =
557+
await repo.findByIdIncludeSoftDelete(1);
558+
expect(afterDeleteIncludeSoftDeleted)
559+
.to.have.property('email')
560+
.equal('john@example.com');
561+
});
562+
563+
it('should soft delete entries with deletedBy set to id', async () => {
564+
await repo.deleteById(1);
565+
try {
566+
await repo.findById(1);
567+
fail();
568+
} catch (e) {
569+
expect(e.message).to.be.equal('EntityNotFound');
570+
}
571+
const afterDeleteIncludeSoftDeleted =
572+
await repo.findByIdIncludeSoftDelete(1);
573+
expect(afterDeleteIncludeSoftDeleted)
574+
.to.have.property('deletedBy')
575+
.equal(userData.id);
576+
});
577+
578+
it('should soft delete entries with deletedBy set to custom key provided', async () => {
579+
await repoWithCustomDeletedByKey.deleteById(1);
580+
try {
581+
await repoWithCustomDeletedByKey.findById(1);
582+
fail();
583+
} catch (e) {
584+
expect(e.message).to.be.equal('EntityNotFound');
585+
}
586+
const afterDeleteIncludeSoftDeleted =
587+
await repoWithCustomDeletedByKey.findByIdIncludeSoftDelete(1);
588+
expect(afterDeleteIncludeSoftDeleted)
589+
.to.have.property('deletedBy')
590+
.equal(userData.username);
591+
});
592+
});
593+
507594
describe('delete', () => {
508595
beforeEach(setupTestData);
509596
afterEach(clearTestData);
@@ -522,18 +609,73 @@ describe('SoftCrudRepositoryMixin', () => {
522609
.to.have.property('email')
523610
.equal('john@example.com');
524611
});
612+
613+
it('should soft delete entries with deletedBy set to id', async () => {
614+
const entity = await repo.findById(1);
615+
await repo.delete(entity);
616+
try {
617+
await repo.findById(1);
618+
fail();
619+
} catch (e) {
620+
expect(e.message).to.be.equal('EntityNotFound');
621+
}
622+
const afterDeleteIncludeSoftDeleted =
623+
await repo.findByIdIncludeSoftDelete(1);
624+
expect(afterDeleteIncludeSoftDeleted)
625+
.to.have.property('deletedBy')
626+
.equal(userData.id);
627+
});
628+
629+
it('should soft delete entries with deletedBy set to custom key provided', async () => {
630+
const entity = await repoWithCustomDeletedByKey.findById(1);
631+
await repoWithCustomDeletedByKey.delete(entity);
632+
try {
633+
await repoWithCustomDeletedByKey.findById(1);
634+
fail();
635+
} catch (e) {
636+
expect(e.message).to.be.equal('EntityNotFound');
637+
}
638+
const afterDeleteIncludeSoftDeleted =
639+
await repoWithCustomDeletedByKey.findByIdIncludeSoftDelete(1);
640+
expect(afterDeleteIncludeSoftDeleted)
641+
.to.have.property('deletedBy')
642+
.equal(userData.username);
643+
});
525644
});
526645

527646
describe('deleteAll', () => {
528647
beforeEach(setupTestData);
529648
afterEach(clearTestData);
649+
530650
it('should soft delete all entries', async () => {
531651
await repo.deleteAll();
532652
const customers = await repo.find();
533653
expect(customers).to.have.length(0);
534654
const afterDeleteAll = await repo.findAll();
535655
expect(afterDeleteAll).to.have.length(4);
536656
});
657+
658+
it('should soft delete entries with deletedBy set to id', async () => {
659+
await repo.deleteAll();
660+
const customers = await repo.find();
661+
expect(customers).to.have.length(0);
662+
const afterDeleteAll = await repo.findAll();
663+
expect(afterDeleteAll).to.have.length(4);
664+
afterDeleteAll.forEach((rec) => {
665+
expect(rec).to.have.property('deletedBy').equal(userData.id);
666+
});
667+
});
668+
669+
it('should soft delete entries with deletedBy set to custom key provided', async () => {
670+
await repoWithCustomDeletedByKey.deleteAll();
671+
const customers = await repoWithCustomDeletedByKey.find();
672+
expect(customers).to.have.length(0);
673+
const afterDeleteAll = await repoWithCustomDeletedByKey.findAll();
674+
expect(afterDeleteAll).to.have.length(4);
675+
afterDeleteAll.forEach((rec) => {
676+
expect(rec).to.have.property('deletedBy').equal(userData.username);
677+
});
678+
});
537679
});
538680

539681
describe('deleteHard', () => {
@@ -571,9 +713,19 @@ describe('SoftCrudRepositoryMixin', () => {
571713
await repo.create({id: 3, email: 'alice@example.com'});
572714
await repo.create({id: 4, email: 'bob@example.com'});
573715
await repo.deleteById(3);
716+
717+
await repoWithCustomDeletedByKey.create({id: 1, email: 'john@example.com'});
718+
await repoWithCustomDeletedByKey.create({id: 2, email: 'mary@example.com'});
719+
await repoWithCustomDeletedByKey.create({
720+
id: 3,
721+
email: 'alice@example.com',
722+
});
723+
await repoWithCustomDeletedByKey.create({id: 4, email: 'bob@example.com'});
724+
await repoWithCustomDeletedByKey.deleteById(3);
574725
}
575726

576727
async function clearTestData() {
577728
await repo.deleteAllHard();
729+
await repoWithCustomDeletedByKey.deleteAllHard();
578730
}
579731
});

0 commit comments

Comments
 (0)