Rules. Переиспользование логики доступов
Rule - это атомарное правило, позволяющее реализовать часть требований для формирования permissions.
Цель: возможность переиспользования логики формирования permissions между policies.
Местоположение
Все правила находятся в директории rules
:
├── modules/
| ├── permissions/
| | ├── domain/
| | | ├── stores/
| | | | ├── PermissionsStore/
| | | | | ├── policies/
| | | | | ├── rules/
| | | | | | |── calcAcceptableAge/
| | | | | | |── calcAccountPayment/
| | | | | | └── index.ts
| | | | ├── PermissionsStore.ts
| | | | └── index.ts
| | | ├── enums.ts
| | | └── index.ts
Rules - приватные функции модуля permissions
Rules недоступны вне permissions модуля и являются частью имплементации PermissionsStore
.
Реализация rules
Rules создаются с помощью createRule
из пакета @astral/permissions:
Пример
Требования
- Чтение книги доступно только если возраст пользователя соответствует доступному возрасту, указанному в книге
- Покупка товара доступна только если возраст пользователя соответствует доступному возрасту, указанному в товаре
Решение
Требуется реализовать два permissions: calcReadingBook
и calcPayment
.
Оба permissions должны реализовать одну и ту же логику проверки возраста пользователя.
Необходимо реализовать rule: calcAcceptableAge
:
modules/permissions/domain/stores/PermissionsStore/rules/calcAcceptableAge
// @astral/permissions в реальном коде необходимо реэкспортировать через shared
import { createRule } from '@astral/permissions';
export const calcAcceptableAge = (
acceptableAge?: number,
userBirthday?: string,
) =>
createRule((allow, deny) => {
if (!acceptableAge) {
return deny(PermissionDenialReason.MissingData);
}
if (!userBirthday) {
return deny(PermissionDenialReason.MissingUserAge);
}
if (getDateYearDiff(new Date(userBirthday), new Date()) < acceptableAge) {
return deny(PermissionDenialReason.NotForYourAge);
}
allow();
});
Далее PaymentPolicyStore
и BooksPolicyStore
используют calcAcceptableAge
для вычисления досту пов:
modules/permissions/domain/stores/PermissionsStore/policies/PaymentPolicyStore
import { calcAcceptableAge } from '../../rules';
export class PaymentPolicyStore {
private readonly policy: PermissionsPolicy;
constructor(
policyManager: PermissionsPolicyManagerStore,
private readonly userRepo: UserRepository,
) {
makeAutoObservable(this, {}, { autoBind: true });
this.policy = policyManager.createPolicy({
name: 'payment',
prepareData: async () => {
await Promise.all([userRepo.getPersonInfoQuery().async()]);
},
});
}
/**
* Возможность оплатить товар
*/
public calcPayment = (acceptableAge: number) =>
this.policy.createPermission((allow, deny) => {
const agePermission = calcAcceptableAge(
acceptableAge,
this.userRepo.getPersonInfoQuery().data?.birthday,
);
if (!agePermission.isAllowed) {
return deny(agePermission.reason);
}
allow();
});
}
modules/permissions/domain/stores/PermissionsStore/policies/BooksPolicyStore
import { calcAcceptableAge } from '../../rules';
export class BooksPolicyStore {
private readonly policy: PermissionsPolicy;
constructor(
policyManager: PermissionsPolicyManagerStore,
private readonly billingRepo: BillingRepository,
private readonly userRepo: UserRepository,
) {
makeAutoObservable(this, {}, { autoBind: true });
this.policy = policyManager.createPolicy({
name: 'books',
prepareData: async () => {
await Promise.all([
this.userRepo.getRolesQuery().async(),
this.userRepo.getPersonInfoQuery().async(),
this.billingRepo.getBillingInfoQuery().async(),
]);
},
});
}
/**
* Возможность прочитать книгу онлайн
*/
public calcReadingOnline = (acceptableAge?: number) => {
return this.policy.createPermission((allow, deny) => {
const agePermission = calcAcceptableAge(
acceptableAge,
this.userRepo.getPersonInfoQuery().data?.birthday,
);
if (!agePermission.isAllowed) {
return deny(agePermission.reason);
}
const billingInfo = this.billingRepo.getBillingInfoQuery().data;
if (!billingInfo?.paid) {
return deny(PermissionDenialReason.NoPayAccount);
}
allow();
});
};
}
Нейминг
Все rules имеют префикс calc
.
✅ Valid
/**
* Вычислить админские доступы
*/
const calcAdminPermissions = (role: string) => {...};
/**
* Вычислить доступность возраста
*/
const calcAcceptableAge = (acceptableAge: string?, userBirthday?: string) => {...};
❌ Invalid
const checkAdminRole = (role: string) => {...};
const checkAcceptableAge = (acceptableAge: string?, userBirthday?: string) => {...};