diff --git a/mnemo_cards_backend/lib/packs/product_availability_manager.dart b/mnemo_cards_backend/lib/packs/product_availability_manager.dart new file mode 100644 index 0000000..b73d366 --- /dev/null +++ b/mnemo_cards_backend/lib/packs/product_availability_manager.dart @@ -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 _publicPackIds; + + ProductAvailabilityManager( + this._packManager, + this._db, { + Set? publicPackIds, + }) : _publicPackIds = publicPackIds ?? const {'10'}; + + /// Проверить доступность пакета для пользователя + /// + /// Возвращает true, если пакет доступен пользователю по одной из причин: + /// - Пакет является публичным (в списке publicPackIds) + /// - Пользователь является администратором + /// - У пользователя есть прямой доступ к пакету (через user_packs) + /// - У пользователя есть активная подписка с фичей SubscriptionFeatureEnum.packs + /// + /// Параметры: + /// - [packId] - ID пакета для проверки + /// - [user] - пользователь (может быть null для неавторизованных) + /// + /// Возвращает: + /// - true если пакет доступен + /// - false если пакет недоступен или не существует + Future 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 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 get publicPackIds => _publicPackIds; + + /// Предоставить пользователю доступ к пакету + /// + /// Параметры: + /// - [userId] - ID пользователя + /// - [packId] - ID пакета + /// - [grantType] - тип предоставления доступа ('purchase', 'promo_code', 'free' и т.д.) + /// + /// Метод автоматически проверяет существование пакета перед предоставлением доступа. + /// Если пакет не существует или отключен, выбрасывается исключение. + Future 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 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 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', + ); + } + } +}