javascript - Criar módulo com escopo com provedores para recurso do módulo
Quando eu uso a importação PolicyModule.forFeature mais de uma vez, a próxima importação do PolicyModule sobrescreve os gates no PolicyStorage.
Quando tento usar PolicyProvider no CandidateModule do CandidateEducationService chamando PolicyProvider
await this.policy.denyAccessUnlessGranted('canDelete', education);
Recebo a exceção Gate pela entidade 'CandidateEducationEntity' não encontrada.
Eu exportei PolicyStorage em CandidateEducationService e obtive array gates com JobPolicy
PolicyStorage {
gates:
[ { policy: [Function: JobPolicy], entity: [Function: JobEntity] } ]
}
Mas eu estava esperando
PolicyStorage {
gates:
[ { policy: [Function: CandidateEducationPolicy], entity: [Function: CandidateEducationEntity] } ]
}
Eu criei um módulo dinâmico PolicyModule
@Module({})
export class PolicyModule {
public static forFeature(gates: PolicyGate[]): DynamicModule {
const providers: Provider[] = [
...gates.map(gate => gate.policy),
{
provide: PolicyStorage,
useValue: new PolicyStorage(gates),
},
PolicyProvider,
];
return {
module: PolicyModule,
imports: [
CommonModule,
],
providers,
exports: providers,
};
}
}
ArmazenamentoDePolítica
@Injectable()
export class PolicyStorage {
constructor(private gates: PolicyGate[]) {
console.log(this.gates);
}
public find(name: string): PolicyGate | null {
return this.gates.find(policy => policy.entity.name === name);
}
}
PolicyProvider@Injectable()
export class PolicyProvider<E, P> {
constructor(
private readonly moduleRef: ModuleRef,
private readonly gateStorage: PolicyStorage,
private readonly appContext: AppContextService,
) {
}
public async denyAccessUnlessGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
if (await this.denies(methodNames, entity, customData)) {
throw new ForbiddenException();
}
}
public async allowAccessIfGranted(methodNames: MethodKeys<P>, entity: E, customData?: any) {
const allowed = await this.allows(methodNames, entity, customData);
if (!allowed) {
throw new ForbiddenException();
}
}
private async allows(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === true);
}
private async denies(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean> {
const results = await this.getPolicyResults(methodNames, entity, customData);
return results.every(res => res === false);
}
private async getPolicyResults(methodNames: MethodKeys<P>, entity: E, customData?: any): Promise<boolean[]> {
const methodNamesArray = Array.isArray(methodNames) ? methodNames : [methodNames];
const gate = this.findByClassName(entity.constructor.name);
const user = this.appContext.get('user');
const policy = await this.moduleRef.get<P>(gate.policy, {strict: false});
const results = [];
for (const methodName of methodNamesArray) {
results.push(!!await policy[methodName as string](entity, user, customData));
}
return results;
}
private findByClassName(name: string) {
const gate = this.gateStorage.find(name);
if (!gate) {
throw new RuntimeException(`Gate by entity '${name}' not found`);
}
return gate;
}
}
Usando módulo em outro módulo. Exemplo:
JobsModule
@Module({
imports: [
TypeOrmModule.forFeature(
[
JobEntity,
],
),
PolicyModule.forFeature([
{
policy: JobPolicy,
entity: JobEntity,
},
]),
],
controllers: [
ManagerJobsController,
],
providers: [
ManagerJobsService,
],
})
export class JobsModule {
}
CandidateModule
@Module({
imports: [
TypeOrmModule.forFeature(
[
CandidateEducationEntity,
],
),
PolicyModule.forFeature([
{
policy: CandidateEducationPolicy,
entity: CandidateEducationEntity,
},
]),
],
controllers: [
CandidateEducationController,
],
providers: [
CandidateEducationService,
],
})
export class CandidateModule {
}
Resposta #1
Atualização:
O Nest v6 introduziu provedores com escopo de requisição, veja esta resposta.
Todos os módulos e seus provedores são singletons. Se você registrar um provedor com o mesmo token duas vezes dentro do mesmo módulo, ele será substituído.
Se você der uma olhada no TypeOrmModule, pode ver que ele registra seus provedores de repositório sob um token personalizado único para cada entidade:
export function getRepositoryToken(entity: Function) {
if (
entity.prototype instanceof Repository ||
entity.prototype instanceof AbstractRepository
) {
return getCustomRepositoryToken(entity);
}
return `${entity.name}Repository`;
}
Então, no seu caso, você poderia ter as funções getPolicyProviderToken e getPolicyStorageToken e ambas registrar e injetar seus provedores sob esses tokens que são únicos para cada módulo importador.
