product availability
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Deploy Mnemo Cards / Deploy Backend (push) Waiting to run
Deploy Mnemo Cards / Deploy Web App (push) Blocked by required conditions
Deploy Mnemo Cards / Final Verification (push) Blocked by required conditions

This commit is contained in:
Dmitry 2026-01-08 20:01:23 +03:00
parent efe1a8f898
commit b5f6a78845

View file

@ -0,0 +1,206 @@
import 'dart:developer';
import 'package:injectable/injectable.dart';
import 'package:mnemo_cards_backend/database/database.dart';
import 'package:mnemo_cards_backend/packs/pack_manager.dart';
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
/// Менеджер для проверки доступности и управления доступом к продуктам (пакам, подпискам) пользователю
@lazySingleton
class ProductAvailabilityManager {
final PackManager _packManager;
final AppDatabase _db;
final Set<String> _publicPackIds;
ProductAvailabilityManager(
this._packManager,
this._db, {
Set<String>? publicPackIds,
}) : _publicPackIds = publicPackIds ?? const {'10'};
/// Проверить доступность пакета для пользователя
///
/// Возвращает true, если пакет доступен пользователю по одной из причин:
/// - Пакет является публичным (в списке publicPackIds)
/// - Пользователь является администратором
/// - У пользователя есть прямой доступ к пакету (через user_packs)
/// - У пользователя есть активная подписка с фичей SubscriptionFeatureEnum.packs
///
/// Параметры:
/// - [packId] - ID пакета для проверки
/// - [user] - пользователь (может быть null для неавторизованных)
///
/// Возвращает:
/// - true если пакет доступен
/// - false если пакет недоступен или не существует
Future<bool> isPackAvailable({
required String packId,
required UserModel? user,
}) async {
// Проверить существование и статус пакета
final pack = await _packManager.getPack(packId);
if (pack == null || !pack.enabled) {
return false;
}
// Публичные паки доступны всем
if (_publicPackIds.contains(packId)) {
return true;
}
// Администраторы имеют доступ ко всем пакетам
if (user?.admin == true) {
return true;
}
// Для неавторизованных пользователей доступны только публичные паки
if (user == null || user.id == null) {
return false;
}
// Проверить прямой доступ через user_packs
final hasDirectAccess = await _db.userDao.hasPackAccess(user.id!, packId);
if (hasDirectAccess) {
return true;
}
// Проверить доступ через подписку
if (user.subscriptionModel != null && user.subscriptionModel!.isActive) {
final hasSubscriptionAccess = user.subscriptionModel!.features.contains(
SubscriptionFeatureEnum.packs,
);
if (hasSubscriptionAccess) {
return true;
}
}
return false;
}
/// Проверить доступность продукта для пользователя
///
/// Универсальный метод для проверки доступности любого типа продукта
///
/// Параметры:
/// - [productId] - ID продукта
/// - [productType] - тип продукта (pack, subscription)
/// - [user] - пользователь (может быть null для неавторизованных)
///
/// Возвращает:
/// - true если продукт доступен
/// - false если продукт недоступен
Future<bool> isProductAvailable({
required String productId,
required MnemoCardsProductType productType,
required UserModel? user,
}) async {
switch (productType) {
case MnemoCardsProductType.pack:
return await isPackAvailable(packId: productId, user: user);
case MnemoCardsProductType.subscription:
// Подписки не проверяются на доступность, они являются продуктами для покупки
// Но можно проверить, есть ли у пользователя уже активная подписка
if (user?.subscriptionModel != null &&
user!.subscriptionModel!.isActive) {
// Пользователь уже имеет активную подписку
// В зависимости от бизнес-логики, можно вернуть true или false
return false; // По умолчанию, если есть активная подписка, покупка новой недоступна
}
return true; // Подписка доступна для покупки
case MnemoCardsProductType.discount:
case MnemoCardsProductType.game:
case MnemoCardsProductType.unknown:
// Для discount, game и unknown типов проверка доступности не применима
// Они являются продуктами для покупки или служебными типами
return false;
}
}
/// Проверить, является ли пакет публичным
bool isPublicPack(String packId) {
return _publicPackIds.contains(packId);
}
/// Получить список публичных пакетов
Set<String> get publicPackIds => _publicPackIds;
/// Предоставить пользователю доступ к пакету
///
/// Параметры:
/// - [userId] - ID пользователя
/// - [packId] - ID пакета
/// - [grantType] - тип предоставления доступа ('purchase', 'promo_code', 'free' и т.д.)
///
/// Метод автоматически проверяет существование пакета перед предоставлением доступа.
/// Если пакет не существует или отключен, выбрасывается исключение.
Future<void> grantPackAccess({
required String userId,
required String packId,
String grantType = 'purchase',
}) async {
// Проверить существование и статус пакета
final pack = await _packManager.getPack(packId);
if (pack == null) {
throw ArgumentError('Pack not found: $packId');
}
if (!pack.enabled) {
throw ArgumentError('Pack is disabled: $packId');
}
await _db.userDao.grantPackAccess(
userId: userId,
packId: packId,
grantType: grantType,
);
log('Granted access to pack $packId for user $userId (type: $grantType)');
}
/// Отозвать доступ пользователя к пакету
///
/// Параметры:
/// - [userId] - ID пользователя
/// - [packId] - ID пакета
Future<void> revokePackAccess({
required String userId,
required String packId,
}) async {
await _db.userDao.revokePackAccess(userId, packId);
log('Revoked access to pack $packId for user $userId');
}
/// Предоставить пользователю доступ к продукту
///
/// Универсальный метод для предоставления доступа к любому типу продукта
///
/// Параметры:
/// - [userId] - ID пользователя
/// - [product] - продукт (DTO)
/// - [grantType] - тип предоставления доступа
Future<void> grantProductAccess({
required String userId,
required MnemoCardsProductDto product,
String grantType = 'purchase',
}) async {
switch (product.type) {
case MnemoCardsProductType.pack:
if (product.id == null) {
throw ArgumentError('Product ID is required for pack');
}
await grantPackAccess(
userId: userId,
packId: product.id!,
grantType: grantType,
);
break;
case MnemoCardsProductType.subscription:
case MnemoCardsProductType.discount:
case MnemoCardsProductType.game:
case MnemoCardsProductType.unknown:
// Для этих типов продуктов логика предоставления доступа находится в других менеджерах
// (например, SubscriptionManager для подписок)
throw ArgumentError(
'Granting access for product type ${product.type} is not supported by ProductAvailabilityManager',
);
}
}
}