diff --git a/packages/core/README.md b/packages/core/README.md index de863f04fb..42f46808a9 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -570,6 +570,12 @@ export class UserRepository extends DefaultUserModifyCrudRepository< ) ``` +The repository by default does not restrict setting up of createdOn and modifiedOn through API or external sources. However, you can restrict it by binding the restrictDateModification property to config like this + +```ts +this.bind(SFCoreBindings.config).to({restrictDateModification: true}); +``` + ![Connector](https://loopback.io/images/9830486.png) #### SequelizeUserModifyCrudRepository diff --git a/packages/core/src/component.ts b/packages/core/src/component.ts index 9a6117b67a..9f6c02a446 100644 --- a/packages/core/src/component.ts +++ b/packages/core/src/component.ts @@ -30,6 +30,7 @@ import {LocaleKey} from './enums'; import {OASBindings, SFCoreBindings} from './keys'; import {TenantContextMiddlewareInterceptorProvider} from './middlewares'; import {TenantIdEncryptionProvider} from './providers/tenantid-encryption.provider'; +import {DefaultUserModifyCrudService} from './services/default-user-modify-crud.service'; import {CoreConfig, addTenantId} from './types'; export class CoreComponent implements Component { @@ -100,6 +101,9 @@ export class CoreComponent implements Component { this.bindings.push(Binding.bind(OASBindings.HiddenEndpoint).to([])); this.bindings.push(Binding.bind(SFCoreBindings.i18n).to(this.localeObj)); this.application.add(createBindingFromClass(OperationSpecEnhancer)); + this.application + .bind(SFCoreBindings.DEFAULT_USER_MODIFY_CRUD_SERVICE) + .toClass(DefaultUserModifyCrudService); } private _setupSwaggerStats(): ExpressRequestHandler | undefined { diff --git a/packages/core/src/keys.ts b/packages/core/src/keys.ts index e5c94468a9..3440bf96b5 100644 --- a/packages/core/src/keys.ts +++ b/packages/core/src/keys.ts @@ -7,7 +7,12 @@ import {ExpressRequestHandler} from '@loopback/rest'; import {SetupDatasourceFn} from 'loopback4-dynamic-datasource'; import {BINDING_PREFIX} from './constants'; import {HttpMethod} from './enums'; -import {CoreConfig, TenantIdEncryptionFn} from './types'; +import {UserModifiableEntity} from './models'; +import { + CoreConfig, + IDefaultUserModifyCrud, + TenantIdEncryptionFn, +} from './types'; export namespace SFCoreBindings { export const i18n = BindingKey.create(`${BINDING_PREFIX}.i18n`); @@ -27,6 +32,10 @@ export namespace SFCoreBindings { BindingKey.create( `sf.packages.core.dynamicDatasourceMiddleware`, ); + + export const DEFAULT_USER_MODIFY_CRUD_SERVICE = BindingKey.create< + IDefaultUserModifyCrud + >(`${BINDING_PREFIX}.services.defaultUserModifyCrudService`); } const hiddenKey = 'sf.oas.hiddenEndpoints'; diff --git a/packages/core/src/repositories/default-transactional-user-modify-repository.base.ts b/packages/core/src/repositories/default-transactional-user-modify-repository.base.ts index ca30ae9a29..be28ff41e9 100644 --- a/packages/core/src/repositories/default-transactional-user-modify-repository.base.ts +++ b/packages/core/src/repositories/default-transactional-user-modify-repository.base.ts @@ -2,6 +2,7 @@ // // This software is released under the MIT License. // https://opensource.org/licenses/MIT +import {inject} from '@loopback/core'; import { Count, DataObject, @@ -15,7 +16,9 @@ import {Options} from 'loopback-datasource-juggler'; import {AuthErrorKeys} from 'loopback4-authentication'; import {DefaultTransactionSoftCrudRepository} from 'loopback4-soft-delete'; import {IAuthUserWithPermissions} from '../components'; +import {SFCoreBindings} from '../keys'; import {UserModifiableEntity} from '../models'; +import {IDefaultUserModifyCrud} from '../types'; export abstract class DefaultTransactionalUserModifyRepository< T extends UserModifiableEntity, @@ -34,6 +37,9 @@ export abstract class DefaultTransactionalUserModifyRepository< super(entityClass, dataSource); } + @inject(SFCoreBindings.DEFAULT_USER_MODIFY_CRUD_SERVICE) + public defaultUserModifyCrudService: IDefaultUserModifyCrud; + async create(entity: DataObject, options?: Options): Promise { let currentUser = await this.getCurrentUser(); currentUser = currentUser ?? options?.currentUser; @@ -43,6 +49,7 @@ export abstract class DefaultTransactionalUserModifyRepository< const uid = currentUser?.userTenantId ?? currentUser?.id; entity.createdBy = uid; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.create(entity); return super.create(entity, options); } @@ -57,6 +64,7 @@ export abstract class DefaultTransactionalUserModifyRepository< entity.createdBy = uid ?? ''; entity.modifiedBy = uid ?? ''; }); + entities = await this.defaultUserModifyCrudService.createAll(entities); return super.createAll(entities, options); } @@ -67,6 +75,7 @@ export abstract class DefaultTransactionalUserModifyRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.save(entity); return super.save(entity, options); } @@ -77,6 +86,7 @@ export abstract class DefaultTransactionalUserModifyRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.update(entity); return super.update(entity, options); } @@ -92,7 +102,11 @@ export abstract class DefaultTransactionalUserModifyRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; - return super.updateAll(data, where, options); + const result = await this.defaultUserModifyCrudService.updateAll( + data, + where, + ); + return super.updateAll(result.data, result.where, options); } async updateById( @@ -107,6 +121,7 @@ export abstract class DefaultTransactionalUserModifyRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; + data = await this.defaultUserModifyCrudService.updateById(id, data); return super.updateById(id, data, options); } @@ -121,6 +136,7 @@ export abstract class DefaultTransactionalUserModifyRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; + data = await this.defaultUserModifyCrudService.replaceById(id, data); return super.replaceById(id, data, options); } } diff --git a/packages/core/src/repositories/default-user-modify-crud.repository.base.ts b/packages/core/src/repositories/default-user-modify-crud.repository.base.ts index 43b6ca994d..e2d32bbd00 100644 --- a/packages/core/src/repositories/default-user-modify-crud.repository.base.ts +++ b/packages/core/src/repositories/default-user-modify-crud.repository.base.ts @@ -15,8 +15,11 @@ import {Options} from 'loopback-datasource-juggler'; import {AuthErrorKeys} from 'loopback4-authentication'; import {SoftCrudRepository} from 'loopback4-soft-delete'; +import {inject} from '@loopback/core'; import {IAuthUserWithPermissions} from '../components'; +import {SFCoreBindings} from '../keys'; import {UserModifiableEntity} from '../models'; +import {IDefaultUserModifyCrud} from '../types'; export class DefaultUserModifyCrudRepository< T extends UserModifiableEntity, @@ -35,6 +38,9 @@ export class DefaultUserModifyCrudRepository< super(entityClass, dataSource); } + @inject(SFCoreBindings.DEFAULT_USER_MODIFY_CRUD_SERVICE) + public defaultUserModifyCrudService: IDefaultUserModifyCrud; + async create(entity: DataObject, options?: Options): Promise { let currentUser = await this.getCurrentUser(); currentUser = currentUser ?? options?.currentUser; @@ -44,6 +50,7 @@ export class DefaultUserModifyCrudRepository< const uid = currentUser?.userTenantId ?? currentUser?.id; entity.createdBy = uid; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.create(entity); return super.create(entity, options); } @@ -58,6 +65,7 @@ export class DefaultUserModifyCrudRepository< entity.createdBy = uid ?? ''; entity.modifiedBy = uid ?? ''; }); + entities = await this.defaultUserModifyCrudService.createAll(entities); return super.createAll(entities, options); } @@ -68,6 +76,7 @@ export class DefaultUserModifyCrudRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.save(entity); return super.save(entity, options); } @@ -78,6 +87,7 @@ export class DefaultUserModifyCrudRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; entity.modifiedBy = uid; + entity = await this.defaultUserModifyCrudService.update(entity); return super.update(entity, options); } @@ -93,7 +103,11 @@ export class DefaultUserModifyCrudRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; - return super.updateAll(data, where, options); + const result = await this.defaultUserModifyCrudService.updateAll( + data, + where, + ); + return super.updateAll(result.data, result.where, options); } async updateById( @@ -108,6 +122,7 @@ export class DefaultUserModifyCrudRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; + data = await this.defaultUserModifyCrudService.updateById(id, data); return super.updateById(id, data, options); } async replaceById( @@ -121,6 +136,7 @@ export class DefaultUserModifyCrudRepository< } const uid = currentUser?.userTenantId ?? currentUser?.id; data.modifiedBy = uid; + data = await this.defaultUserModifyCrudService.replaceById(id, data); return super.replaceById(id, data, options); } } diff --git a/packages/core/src/services/default-user-modify-crud.service.ts b/packages/core/src/services/default-user-modify-crud.service.ts new file mode 100644 index 0000000000..8ca33be2d0 --- /dev/null +++ b/packages/core/src/services/default-user-modify-crud.service.ts @@ -0,0 +1,75 @@ +import {BindingScope, inject, injectable} from '@loopback/core'; +import {DataObject, Where} from '@loopback/repository'; +import {SFCoreBindings} from '../keys'; +import {UserModifiableEntity} from '../models'; +import {CoreConfig, IDefaultUserModifyCrud} from '../types'; + +@injectable({scope: BindingScope.TRANSIENT}) +export class DefaultUserModifyCrudService + implements IDefaultUserModifyCrud +{ + constructor( + @inject(SFCoreBindings.config, {optional: true}) + private readonly coreConfig: CoreConfig, + ) {} + create(data: DataObject): Promise> { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(data); + } + return Promise.resolve(data); + } + createAll(data: DataObject[]): Promise[]> { + if (this.coreConfig?.restrictDateModification) { + data.forEach(d => { + delete d.createdOn; + delete d.modifiedOn; + }); + } + return Promise.resolve(data); + } + save(entity: T): Promise { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(entity); + } + return Promise.resolve(entity); + } + update(data: T): Promise { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(data); + } + return Promise.resolve(data); + } + + updateAll( + data: DataObject, + where?: Where, + ): Promise<{data: DataObject; where: Where}> { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(data).then(d => ({ + data: d, + where: where ?? ({} as Where), + })); + } + return Promise.resolve({data, where: where ?? ({} as Where)}); + } + updateById(id: ID, data: DataObject): Promise> { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(data); + } + return Promise.resolve(data); + } + replaceById(id: ID, data: DataObject): Promise> { + if (this.coreConfig?.restrictDateModification) { + return this.removeDateFields(data); + } + return Promise.resolve(data); + } + + private async removeDateFields | T>( + data: S, + ): Promise { + delete data.createdOn; + delete data.modifiedOn; + return data; + } +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 2a4bbc3d55..f621a082b9 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -3,16 +3,17 @@ // This software is released under the MIT License. // https://opensource.org/licenses/MIT +import {DataObject, Where} from '@loopback/repository'; import CryptoJS from 'crypto-js'; import {IncomingMessage, ServerResponse} from 'http'; import {AnyObject} from 'loopback-datasource-juggler'; import {SWStats} from 'swagger-stats'; +import {UserModifiableEntity} from './models'; export interface IServiceConfig { useCustomSequence: boolean; useSequelize?: boolean; } - export type OASPathDefinition = AnyObject; export interface CoreConfig { @@ -52,6 +53,7 @@ export interface CoreConfig { username?: string, password?: string, ) => boolean; + restrictDateModification?: boolean; } /** @@ -88,3 +90,16 @@ export type TenantIdEncryptionFn = ( secretKey: string, tenantId: string, ) => Promise; + +export interface IDefaultUserModifyCrud { + create(data: DataObject): Promise>; + createAll(data: DataObject[]): Promise[]>; + save(entity: T): Promise; + update(data: T): Promise; + updateAll( + data: DataObject, + where?: Where, + ): Promise<{data: DataObject; where: Where}>; + updateById(id: ID, data: DataObject): Promise>; + replaceById(id: ID, data: DataObject): Promise>; +}