Создайте модуль с областями действия для поставщиков - PullRequest
0 голосов
/ 09 февраля 2019

Когда я использую импорт PolicyModule.forFeature более одного раза, следующий импорт PolicyModule переопределяет ворота в PolicyStorage.
Когда я пытаюсь использовать PolicyProvider в CandidateModule 's CandidateEducationService позвонив PolicyProvider

await this.policy.denyAccessUnlessGranted('canDelete', education);

Я получаю исключение Gate by entity 'CandidateEducationEntity' not found.

Я вывел PolicyStorage в CandidateEducationService и получил ворота массива с JobPolicy

PolicyStorage {
  gates:
    [ { policy: [Function: JobPolicy], entity: [Function: JobEntity] } ]
}

Но я ожидал

PolicyStorage {
  gates:
    [ { policy: [Function: CandidateEducationPolicy], entity: [Function: CandidateEducationEntity] } ]
}

Я создал динамический модуль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,
        };
    }
}

PolicyStorage

@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;
    }
}

Использование модуля в другом модуле.Пример:
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 {
}

1 Ответ

0 голосов
/ 10 февраля 2019

Обновление:

В Nest v6 введены провайдеры в области запросов, см. этот ответ .


Все модули и их провайдеры являются одиночными.Если вы зарегистрируете провайдера под одним и тем же токеном дважды в одном и том же модуле, он будет переопределен.

Если вы посмотрите на модуль TypeOrmModule, вы увидите, что он регистрирует своих провайдеров репозитория подуникальный пользовательский токен для каждой сущности:

export function getRepositoryToken(entity: Function) {
  if (
    entity.prototype instanceof Repository ||
    entity.prototype instanceof AbstractRepository
  ) {
    return getCustomRepositoryToken(entity);
  }
  return `${entity.name}Repository`;
}

Таким образом, в вашем случае вы можете использовать функции getPolicyProviderToken и getPolicyStorageToken и одновременно регистрировать и вводить своих провайдеров под этими токенами, которыеуникальны для каждого импортирующего модуля.

...