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
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:
parent
e3afe9888c
commit
829542714a
19 changed files with 219 additions and 207 deletions
|
|
@ -20,6 +20,7 @@ import '../../packs/free_packs_distributor.dart' as _i1062;
|
||||||
import '../../packs/pack_dto_converter.dart' as _i433;
|
import '../../packs/pack_dto_converter.dart' as _i433;
|
||||||
import '../../packs/pack_manager.dart' as _i833;
|
import '../../packs/pack_manager.dart' as _i833;
|
||||||
import '../../packs/pack_repository.dart' as _i258;
|
import '../../packs/pack_repository.dart' as _i258;
|
||||||
|
import '../../packs/product_availability_manager.dart' as _i797;
|
||||||
import '../../packs/products_price_resolver.dart' as _i908;
|
import '../../packs/products_price_resolver.dart' as _i908;
|
||||||
import '../../promo_codes/promo_codes_manager.dart' as _i151;
|
import '../../promo_codes/promo_codes_manager.dart' as _i151;
|
||||||
import '../../statistics/achievement_manager.dart' as _i802;
|
import '../../statistics/achievement_manager.dart' as _i802;
|
||||||
|
|
@ -158,20 +159,6 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh.lazySingleton<_i985.TasksApiV2>(
|
gh.lazySingleton<_i985.TasksApiV2>(
|
||||||
() => _i985.TasksApiV2(gh<_i586.TaskManager>()),
|
() => _i985.TasksApiV2(gh<_i586.TaskManager>()),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i1009.PaymentManager>(
|
|
||||||
() => _i1009.PaymentManager(
|
|
||||||
gh<_i1072.AppDatabase>(),
|
|
||||||
gh<_i950.UserRepository>(),
|
|
||||||
gh<_i988.YooMoneyHandler>(),
|
|
||||||
gh<_i222.RustorePurchaseHandler>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i151.PromoCodesManager>(
|
|
||||||
() => _i151.PromoCodesManager(
|
|
||||||
gh<_i1072.AppDatabase>(),
|
|
||||||
gh<_i1009.PaymentManager>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.factory<_i1015.AdminPacksApiV2>(
|
gh.factory<_i1015.AdminPacksApiV2>(
|
||||||
() => _i1015.AdminPacksApiV2(
|
() => _i1015.AdminPacksApiV2(
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
|
|
@ -184,57 +171,15 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i273.PromocodesApiV2>(
|
|
||||||
() => _i273.PromocodesApiV2(gh<_i151.PromoCodesManager>()),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i908.ProductsPriceResolver>(
|
gh.lazySingleton<_i908.ProductsPriceResolver>(
|
||||||
() => _i908.ProductsPriceResolver(
|
() => _i908.ProductsPriceResolver(
|
||||||
gh<_i891.DiscountsManager>(),
|
gh<_i891.DiscountsManager>(),
|
||||||
gh<_i950.UserRepository>(),
|
gh<_i950.UserRepository>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i1062.FreePacksDistributor>(
|
|
||||||
() => _i1062.FreePacksDistributor(
|
|
||||||
gh<_i950.UserRepository>(),
|
|
||||||
gh<_i258.PackRepository>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i735.CheckPaymentTask>(
|
|
||||||
() => _i735.CheckPaymentTask(
|
|
||||||
gh<_i1009.PaymentManager>(),
|
|
||||||
gh<_i1072.AppDatabase>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i858.DiscountsApiV2>(
|
gh.lazySingleton<_i858.DiscountsApiV2>(
|
||||||
() => _i858.DiscountsApiV2(gh<_i891.DiscountsManager>()),
|
() => _i858.DiscountsApiV2(gh<_i891.DiscountsManager>()),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i280.UserManager>(
|
|
||||||
() => _i280.UserManager(
|
|
||||||
gh<_i1072.AppDatabase>(),
|
|
||||||
gh<_i950.UserRepository>(),
|
|
||||||
gh<_i1062.FreePacksDistributor>(),
|
|
||||||
gh<_i71.SessionTracker>(),
|
|
||||||
gh<_i1029.StatisticsCalculator>(),
|
|
||||||
gh<_i802.AchievementManager>(),
|
|
||||||
gh<_i909.WordStatisticsManager>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i247.UsersApiV2>(
|
|
||||||
() => _i247.UsersApiV2(
|
|
||||||
gh<_i280.UserManager>(),
|
|
||||||
gh<_i1009.PaymentManager>(),
|
|
||||||
gh<_i1029.StatisticsCalculator>(),
|
|
||||||
gh<_i950.UserRepository>(),
|
|
||||||
gh<_i1072.AppDatabase>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i483.AdminAuthApiV2>(
|
|
||||||
() => _i483.AdminAuthApiV2(
|
|
||||||
gh<_i240.TelegramAuthCodeService>(),
|
|
||||||
gh<_i280.UserManager>(),
|
|
||||||
gh<_i108.JwtService>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.lazySingleton<_i433.PackDtoConverter>(
|
gh.lazySingleton<_i433.PackDtoConverter>(
|
||||||
() => _i433.PackDtoConverter(
|
() => _i433.PackDtoConverter(
|
||||||
gh<_i908.ProductsPriceResolver>(),
|
gh<_i908.ProductsPriceResolver>(),
|
||||||
|
|
@ -248,6 +193,37 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh<_i433.PackDtoConverter>(),
|
gh<_i433.PackDtoConverter>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i797.ProductAvailabilityManager>(
|
||||||
|
() => _i797.ProductAvailabilityManager(
|
||||||
|
gh<_i833.PackManager>(),
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
publicPackIds: gh<Set<String>>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
gh.lazySingleton<_i1062.FreePacksDistributor>(
|
||||||
|
() => _i1062.FreePacksDistributor(
|
||||||
|
gh<_i797.ProductAvailabilityManager>(),
|
||||||
|
gh<_i258.PackRepository>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
gh.lazySingleton<_i280.UserManager>(
|
||||||
|
() => _i280.UserManager(
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
gh<_i950.UserRepository>(),
|
||||||
|
gh<_i1062.FreePacksDistributor>(),
|
||||||
|
gh<_i71.SessionTracker>(),
|
||||||
|
gh<_i1029.StatisticsCalculator>(),
|
||||||
|
gh<_i802.AchievementManager>(),
|
||||||
|
gh<_i909.WordStatisticsManager>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
gh.lazySingleton<_i483.AdminAuthApiV2>(
|
||||||
|
() => _i483.AdminAuthApiV2(
|
||||||
|
gh<_i240.TelegramAuthCodeService>(),
|
||||||
|
gh<_i280.UserManager>(),
|
||||||
|
gh<_i108.JwtService>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.lazySingleton<_i560.UserManager>(
|
gh.lazySingleton<_i560.UserManager>(
|
||||||
() => _i560.UserManager(
|
() => _i560.UserManager(
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
|
|
@ -260,12 +236,13 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh.lazySingleton<_i561.MnemoShelf>(
|
gh.lazySingleton<_i561.MnemoShelf>(
|
||||||
() => _i561.MnemoShelf(gh<_i280.UserManager>()),
|
() => _i561.MnemoShelf(gh<_i280.UserManager>()),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i69.PurchasesApiV2>(
|
gh.lazySingleton<_i1009.PaymentManager>(
|
||||||
() => _i69.PurchasesApiV2(
|
() => _i1009.PaymentManager(
|
||||||
gh<_i1009.PaymentManager>(),
|
|
||||||
gh<_i833.PackManager>(),
|
|
||||||
gh<_i950.UserRepository>(),
|
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
|
gh<_i950.UserRepository>(),
|
||||||
|
gh<_i797.ProductAvailabilityManager>(),
|
||||||
|
gh<_i988.YooMoneyHandler>(),
|
||||||
|
gh<_i222.RustorePurchaseHandler>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.lazySingleton<_i800.PacksApiV2>(
|
gh.lazySingleton<_i800.PacksApiV2>(
|
||||||
|
|
@ -273,11 +250,17 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh<_i833.PackManager>(),
|
gh<_i833.PackManager>(),
|
||||||
gh<_i259.TestManager>(),
|
gh<_i259.TestManager>(),
|
||||||
gh<_i258.PackRepository>(),
|
gh<_i258.PackRepository>(),
|
||||||
gh<_i950.UserRepository>(),
|
gh<_i797.ProductAvailabilityManager>(),
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
gh<_i747.MinioService>(),
|
gh<_i747.MinioService>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i735.CheckPaymentTask>(
|
||||||
|
() => _i735.CheckPaymentTask(
|
||||||
|
gh<_i1009.PaymentManager>(),
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.lazySingleton<_i170.TestsApiV2>(
|
gh.lazySingleton<_i170.TestsApiV2>(
|
||||||
() => _i170.TestsApiV2(
|
() => _i170.TestsApiV2(
|
||||||
gh<_i259.TestManager>(),
|
gh<_i259.TestManager>(),
|
||||||
|
|
@ -285,6 +268,15 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i69.PurchasesApiV2>(
|
||||||
|
() => _i69.PurchasesApiV2(
|
||||||
|
gh<_i1009.PaymentManager>(),
|
||||||
|
gh<_i833.PackManager>(),
|
||||||
|
gh<_i950.UserRepository>(),
|
||||||
|
gh<_i797.ProductAvailabilityManager>(),
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
gh.lazySingleton<_i52.AuthApiV2>(
|
gh.lazySingleton<_i52.AuthApiV2>(
|
||||||
() => _i52.AuthApiV2(
|
() => _i52.AuthApiV2(
|
||||||
gh<_i1072.AppDatabase>(),
|
gh<_i1072.AppDatabase>(),
|
||||||
|
|
@ -294,6 +286,24 @@ extension GetItInjectableX on _i174.GetIt {
|
||||||
gh<_i240.TelegramAuthCodeService>(),
|
gh<_i240.TelegramAuthCodeService>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
gh.lazySingleton<_i247.UsersApiV2>(
|
||||||
|
() => _i247.UsersApiV2(
|
||||||
|
gh<_i280.UserManager>(),
|
||||||
|
gh<_i1009.PaymentManager>(),
|
||||||
|
gh<_i1029.StatisticsCalculator>(),
|
||||||
|
gh<_i950.UserRepository>(),
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
gh.lazySingleton<_i151.PromoCodesManager>(
|
||||||
|
() => _i151.PromoCodesManager(
|
||||||
|
gh<_i1072.AppDatabase>(),
|
||||||
|
gh<_i1009.PaymentManager>(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
gh.lazySingleton<_i273.PromocodesApiV2>(
|
||||||
|
() => _i273.PromocodesApiV2(gh<_i151.PromoCodesManager>()),
|
||||||
|
);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import 'package:drift/drift.dart' as drift;
|
||||||
import 'payment_drift_extension.dart';
|
import 'payment_drift_extension.dart';
|
||||||
import 'rustore/rustore_purchase_handler.dart';
|
import 'rustore/rustore_purchase_handler.dart';
|
||||||
import 'yoo_money.dart';
|
import 'yoo_money.dart';
|
||||||
|
import '../../packs/product_availability_manager.dart';
|
||||||
import '../../user/user_repository.dart';
|
import '../../user/user_repository.dart';
|
||||||
|
|
||||||
/// Result of creating YooKassa payment URL
|
/// Result of creating YooKassa payment URL
|
||||||
|
|
@ -27,6 +28,7 @@ class YookassaPaymentResult {
|
||||||
class PaymentManager {
|
class PaymentManager {
|
||||||
final AppDatabase _db;
|
final AppDatabase _db;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
|
final ProductAvailabilityManager _productAvailabilityManager;
|
||||||
late final GooglePlayPurchaseHandler googlePurchaseHandler;
|
late final GooglePlayPurchaseHandler googlePurchaseHandler;
|
||||||
final YooMoneyHandler _yooMoneyHandler;
|
final YooMoneyHandler _yooMoneyHandler;
|
||||||
final RustorePurchaseHandler _rustorePurchaseHandler;
|
final RustorePurchaseHandler _rustorePurchaseHandler;
|
||||||
|
|
@ -34,6 +36,7 @@ class PaymentManager {
|
||||||
PaymentManager(
|
PaymentManager(
|
||||||
this._db,
|
this._db,
|
||||||
this._userRepository,
|
this._userRepository,
|
||||||
|
this._productAvailabilityManager,
|
||||||
this._yooMoneyHandler,
|
this._yooMoneyHandler,
|
||||||
this._rustorePurchaseHandler,
|
this._rustorePurchaseHandler,
|
||||||
);
|
);
|
||||||
|
|
@ -79,13 +82,12 @@ class PaymentManager {
|
||||||
) async {
|
) async {
|
||||||
await _db.transaction(() async {
|
await _db.transaction(() async {
|
||||||
if (product.type == MnemoCardsProductType.pack && product.id != null) {
|
if (product.type == MnemoCardsProductType.pack && product.id != null) {
|
||||||
final packId = product.id!;
|
await _productAvailabilityManager.grantProductAccess(
|
||||||
await _userRepository.grantPackAccess(
|
|
||||||
userId: userId,
|
userId: userId,
|
||||||
packId: packId,
|
product: product,
|
||||||
grantType: 'promo_code',
|
grantType: 'promo_code',
|
||||||
);
|
);
|
||||||
log('Granted pack $packId to user $userId via promo code');
|
log('Granted pack ${product.id} to user $userId via promo code');
|
||||||
} else if (product.type == MnemoCardsProductType.subscription &&
|
} else if (product.type == MnemoCardsProductType.subscription &&
|
||||||
product.id != null) {
|
product.id != null) {
|
||||||
final planId = product.id!;
|
final planId = product.id!;
|
||||||
|
|
@ -99,7 +101,7 @@ class PaymentManager {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
start: PgDateTime(now),
|
start: PgDateTime(now),
|
||||||
finish: PgDateTime(endDate),
|
finish: PgDateTime(endDate),
|
||||||
features: drift.Value(plan.features as List<dynamic>),
|
features: drift.Value(plan.features),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
log('Granted subscription $planId to user $userId via promo code');
|
log('Granted subscription $planId to user $userId via promo code');
|
||||||
|
|
@ -120,7 +122,10 @@ class PaymentManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получить пользователя
|
// Получить пользователя
|
||||||
final userModel = await _userRepository.getUserById(payment.userId, withData: true);
|
final userModel = await _userRepository.getUserById(
|
||||||
|
payment.userId,
|
||||||
|
withData: true,
|
||||||
|
);
|
||||||
if (userModel == null) {
|
if (userModel == null) {
|
||||||
log('User not found: ${payment.userId}');
|
log('User not found: ${payment.userId}');
|
||||||
return;
|
return;
|
||||||
|
|
@ -160,7 +165,7 @@ class PaymentManager {
|
||||||
await _db.transaction(() async {
|
await _db.transaction(() async {
|
||||||
// Дать доступ к пакетам
|
// Дать доступ к пакетам
|
||||||
for (final packId in packIds) {
|
for (final packId in packIds) {
|
||||||
await _userRepository.grantPackAccess(
|
await _productAvailabilityManager.grantPackAccess(
|
||||||
userId: payment.userId,
|
userId: payment.userId,
|
||||||
packId: packId,
|
packId: packId,
|
||||||
grantType: 'purchase',
|
grantType: 'purchase',
|
||||||
|
|
|
||||||
|
|
@ -61,25 +61,15 @@ class AdminAnalyticsApiV2 {
|
||||||
final packCount = await _db.packDao.countPacks();
|
final packCount = await _db.packDao.countPacks();
|
||||||
|
|
||||||
// Get all packs and filter in memory
|
// Get all packs and filter in memory
|
||||||
final enabledPacks = await _db.packDao.getAllPacks(
|
final enabledPacks = await _db.packDao.getAllPacks(enabledOnly: true);
|
||||||
enabledOnly: true,
|
|
||||||
);
|
|
||||||
final enabledPackCount = enabledPacks.length;
|
final enabledPackCount = enabledPacks.length;
|
||||||
|
|
||||||
final paymentCount = await _db.paymentDao.countAllPayments();
|
final paymentCount = await _db.paymentDao.countAllPayments();
|
||||||
|
|
||||||
// Get recent users (last 10)
|
// Get recent users (last 10)
|
||||||
final allUsers = await _userRepository.getAllUsers(
|
final allUsers = await _userRepository.getAllUsers(limit: 10);
|
||||||
limit: 10,
|
|
||||||
);
|
|
||||||
final recentUsers = allUsers
|
final recentUsers = allUsers
|
||||||
.map(
|
.map((u) => {'id': u.id, 'name': u.name, 'email': u.email})
|
||||||
(u) => {
|
|
||||||
'id': u.id,
|
|
||||||
'name': u.name,
|
|
||||||
'email': u.email,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
// Get top packs by user count
|
// Get top packs by user count
|
||||||
|
|
|
||||||
|
|
@ -160,7 +160,10 @@ class AdminUsersApiV2 {
|
||||||
}, statusCode: 400);
|
}, statusCode: 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
final userModel = await _userRepository.getUserById(userId, includePacks: true);
|
final userModel = await _userRepository.getUserById(
|
||||||
|
userId,
|
||||||
|
includePacks: true,
|
||||||
|
);
|
||||||
if (userModel == null) {
|
if (userModel == null) {
|
||||||
return _json({
|
return _json({
|
||||||
'error': 'User not found',
|
'error': 'User not found',
|
||||||
|
|
@ -219,7 +222,9 @@ class AdminUsersApiV2 {
|
||||||
|
|
||||||
if (isUpdate) {
|
if (isUpdate) {
|
||||||
// Update existing user
|
// Update existing user
|
||||||
final existingUserModel = await _userRepository.getUserById(userDto.id!);
|
final existingUserModel = await _userRepository.getUserById(
|
||||||
|
userDto.id!,
|
||||||
|
);
|
||||||
if (existingUserModel == null) {
|
if (existingUserModel == null) {
|
||||||
return _json({
|
return _json({
|
||||||
'error': 'User not found',
|
'error': 'User not found',
|
||||||
|
|
@ -254,7 +259,10 @@ class AdminUsersApiV2 {
|
||||||
await _userRepository.updateUserPartial(updates);
|
await _userRepository.updateUserPartial(updates);
|
||||||
|
|
||||||
// Get updated user
|
// Get updated user
|
||||||
final updatedUserModel = await _userRepository.getUserById(userDto.id!, includePacks: true);
|
final updatedUserModel = await _userRepository.getUserById(
|
||||||
|
userDto.id!,
|
||||||
|
includePacks: true,
|
||||||
|
);
|
||||||
if (updatedUserModel == null) {
|
if (updatedUserModel == null) {
|
||||||
return _json({
|
return _json({
|
||||||
'error': 'Database error',
|
'error': 'Database error',
|
||||||
|
|
@ -275,7 +283,9 @@ class AdminUsersApiV2 {
|
||||||
: 'admin_created_${DateTime.now().millisecondsSinceEpoch}';
|
: 'admin_created_${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
|
||||||
// Check if user with this externalUserId already exists
|
// Check if user with this externalUserId already exists
|
||||||
final existingUserModel = await _userRepository.getUserByExternalId(externalUserId);
|
final existingUserModel = await _userRepository.getUserByExternalId(
|
||||||
|
externalUserId,
|
||||||
|
);
|
||||||
if (existingUserModel != null) {
|
if (existingUserModel != null) {
|
||||||
return _json({
|
return _json({
|
||||||
'error': 'User already exists',
|
'error': 'User already exists',
|
||||||
|
|
@ -307,7 +317,10 @@ class AdminUsersApiV2 {
|
||||||
userData: userDataCompanion,
|
userData: userDataCompanion,
|
||||||
);
|
);
|
||||||
|
|
||||||
final createdUserModel = await _userRepository.getUserById(userId, includePacks: true);
|
final createdUserModel = await _userRepository.getUserById(
|
||||||
|
userId,
|
||||||
|
includePacks: true,
|
||||||
|
);
|
||||||
if (createdUserModel == null) {
|
if (createdUserModel == null) {
|
||||||
return _json({
|
return _json({
|
||||||
'error': 'Database error',
|
'error': 'Database error',
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,11 @@ import 'package:mnemo_cards_backend/packs/card_image_storage.dart';
|
||||||
import 'package:mnemo_cards_backend/packs/card_model_extension.dart';
|
import 'package:mnemo_cards_backend/packs/card_model_extension.dart';
|
||||||
import 'package:mnemo_cards_backend/packs/pack_manager.dart' show PackManager;
|
import 'package:mnemo_cards_backend/packs/pack_manager.dart' show PackManager;
|
||||||
import 'package:mnemo_cards_backend/packs/pack_repository.dart';
|
import 'package:mnemo_cards_backend/packs/pack_repository.dart';
|
||||||
|
import 'package:mnemo_cards_backend/packs/product_availability_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/packs/voice_storage.dart';
|
import 'package:mnemo_cards_backend/packs/voice_storage.dart';
|
||||||
import 'package:mnemo_cards_backend/storage/minio_config.dart';
|
import 'package:mnemo_cards_backend/storage/minio_config.dart';
|
||||||
import 'package:mnemo_cards_backend/storage/minio_service.dart';
|
import 'package:mnemo_cards_backend/storage/minio_service.dart';
|
||||||
import 'package:mnemo_cards_backend/tests/test_manager.dart';
|
import 'package:mnemo_cards_backend/tests/test_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
import 'package:shelf/shelf.dart';
|
import 'package:shelf/shelf.dart';
|
||||||
import 'package:shelf_open_api/shelf_open_api.dart';
|
import 'package:shelf_open_api/shelf_open_api.dart';
|
||||||
|
|
@ -33,7 +32,7 @@ class PacksApiV2 {
|
||||||
final PackManager _packManager;
|
final PackManager _packManager;
|
||||||
final TestManager _testManager;
|
final TestManager _testManager;
|
||||||
final PackRepository _packRepository;
|
final PackRepository _packRepository;
|
||||||
final UserRepository _userRepository;
|
final ProductAvailabilityManager _productAvailabilityManager;
|
||||||
final AppDatabase _db;
|
final AppDatabase _db;
|
||||||
final MinioService _minioService;
|
final MinioService _minioService;
|
||||||
|
|
||||||
|
|
@ -41,7 +40,7 @@ class PacksApiV2 {
|
||||||
this._packManager,
|
this._packManager,
|
||||||
this._testManager,
|
this._testManager,
|
||||||
this._packRepository,
|
this._packRepository,
|
||||||
this._userRepository,
|
this._productAvailabilityManager,
|
||||||
this._db,
|
this._db,
|
||||||
this._minioService,
|
this._minioService,
|
||||||
);
|
);
|
||||||
|
|
@ -66,12 +65,6 @@ class PacksApiV2 {
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
);
|
);
|
||||||
|
|
||||||
Response _conflict(String message) => Response(
|
|
||||||
409,
|
|
||||||
body: jsonEncode({'error': 'Conflict', 'message': message}),
|
|
||||||
headers: {'Content-Type': 'application/json'},
|
|
||||||
);
|
|
||||||
|
|
||||||
Response _forbidden([String? message]) => Response(
|
Response _forbidden([String? message]) => Response(
|
||||||
403,
|
403,
|
||||||
body: jsonEncode({
|
body: jsonEncode({
|
||||||
|
|
@ -289,10 +282,11 @@ class PacksApiV2 {
|
||||||
final packJson = packDto.toJson();
|
final packJson = packDto.toJson();
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
final isPurchased =
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
await _isPackPurchased(user.id!, packId) ||
|
packId: packId,
|
||||||
(await _hasSubscriptionAccess(user));
|
user: user,
|
||||||
packJson['isPurchased'] = isPurchased;
|
);
|
||||||
|
packJson['isPurchased'] = isAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _ok(packJson);
|
return _ok(packJson);
|
||||||
|
|
@ -331,10 +325,11 @@ class PacksApiV2 {
|
||||||
|
|
||||||
// Include purchase status in response if user is authenticated
|
// Include purchase status in response if user is authenticated
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
final isPurchased =
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
await _isPackPurchased(user.id!, packId) ||
|
packId: packId,
|
||||||
(await _hasSubscriptionAccess(user));
|
user: user,
|
||||||
packJson['isPurchased'] = isPurchased;
|
);
|
||||||
|
packJson['isPurchased'] = isAvailable;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _ok(packJson);
|
return _ok(packJson);
|
||||||
|
|
@ -834,11 +829,6 @@ class PacksApiV2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if user purchased a pack
|
|
||||||
Future<bool> _isPackPurchased(String userId, String packId) async {
|
|
||||||
return await _userRepository.hasPackAccess(userId, packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// GET /api/v2/packs/{packId}/tests
|
/// GET /api/v2/packs/{packId}/tests
|
||||||
/// Get tests for a pack
|
/// Get tests for a pack
|
||||||
@Route.get('/packs/<packId>/tests')
|
@Route.get('/packs/<packId>/tests')
|
||||||
|
|
@ -900,16 +890,6 @@ class PacksApiV2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper method to check if user has subscription access
|
|
||||||
Future<bool> _hasSubscriptionAccess(UserModel user) async {
|
|
||||||
// Check subscription features from user model
|
|
||||||
final subscription = user.subscriptionModel;
|
|
||||||
if (subscription == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return subscription.features.contains(SubscriptionFeatureEnum.packs);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert Drift VoiceModel to VoiceDto
|
/// Convert Drift VoiceModel to VoiceDto
|
||||||
VoiceDto _voiceModelToDto(
|
VoiceDto _voiceModelToDto(
|
||||||
drift.VoiceModel voice, {
|
drift.VoiceModel voice, {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
|
||||||
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
import 'package:mnemo_cards_backend/api/purchase/payment_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_backend/packs/pack_manager.dart';
|
import 'package:mnemo_cards_backend/packs/pack_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/packs/products_price_resolver.dart';
|
import 'package:mnemo_cards_backend/packs/product_availability_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||||
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
import 'package:mnemo_cards_common/mnemo_cards_common.dart';
|
||||||
|
|
@ -23,12 +23,14 @@ class PurchasesApiV2 {
|
||||||
final PaymentManager _paymentManager;
|
final PaymentManager _paymentManager;
|
||||||
final PackManager _packManager;
|
final PackManager _packManager;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
|
final ProductAvailabilityManager _productAvailabilityManager;
|
||||||
final AppDatabase _db;
|
final AppDatabase _db;
|
||||||
|
|
||||||
PurchasesApiV2(
|
PurchasesApiV2(
|
||||||
this._paymentManager,
|
this._paymentManager,
|
||||||
this._packManager,
|
this._packManager,
|
||||||
this._userRepository,
|
this._userRepository,
|
||||||
|
this._productAvailabilityManager,
|
||||||
this._db,
|
this._db,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -101,11 +103,14 @@ class PurchasesApiV2 {
|
||||||
print(
|
print(
|
||||||
'🔍 createPackPurchase: Checking pack access for userId=${user.id}, packId=$packId',
|
'🔍 createPackPurchase: Checking pack access for userId=${user.id}, packId=$packId',
|
||||||
);
|
);
|
||||||
// Check if already purchased
|
// Check if already purchased or available through subscription
|
||||||
final hasAccess = await _userRepository.hasPackAccess(user.id!, packId);
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
if (hasAccess) {
|
packId: packId,
|
||||||
print('❌ createPackPurchase: Pack already purchased');
|
user: user,
|
||||||
return _badRequest('Pack is already purchased');
|
);
|
||||||
|
if (isAvailable) {
|
||||||
|
print('❌ createPackPurchase: Pack already available');
|
||||||
|
return _badRequest('Pack is already available');
|
||||||
}
|
}
|
||||||
print('✅ createPackPurchase: Pack not purchased yet');
|
print('✅ createPackPurchase: Pack not purchased yet');
|
||||||
|
|
||||||
|
|
@ -207,10 +212,13 @@ class PurchasesApiV2 {
|
||||||
return _notFound('Pack not found');
|
return _notFound('Pack not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if already purchased
|
// Check if already purchased or available through subscription
|
||||||
final hasAccess = await _userRepository.hasPackAccess(user.id!, productId);
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
if (hasAccess) {
|
packId: productId,
|
||||||
return _badRequest('Pack is already purchased');
|
user: user,
|
||||||
|
);
|
||||||
|
if (isAvailable) {
|
||||||
|
return _badRequest('Pack is already available');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get price (with discounts if applicable)
|
// Get price (with discounts if applicable)
|
||||||
|
|
@ -370,24 +378,30 @@ class PurchasesApiV2 {
|
||||||
return _notFound('Pack not found');
|
return _notFound('Pack not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check purchase status
|
// Check availability (includes both direct purchase and subscription)
|
||||||
final isPurchased = await _userRepository.hasPackAccess(user.id!, packId);
|
final isAvailable = await _productAvailabilityManager.isPackAvailable(
|
||||||
|
packId: packId,
|
||||||
|
user: user,
|
||||||
|
);
|
||||||
|
|
||||||
// Check subscription access
|
// Check direct purchase access separately
|
||||||
|
final hasDirectAccess = await _db.userDao.hasPackAccess(user.id!, packId);
|
||||||
|
|
||||||
|
// Check subscription access separately
|
||||||
final activeSubscription = await _db.subscriptionDao
|
final activeSubscription = await _db.subscriptionDao
|
||||||
.getActiveSubscription(user.id!);
|
.getActiveSubscription(user.id!);
|
||||||
final hasSubscriptionAccess =
|
final hasSubscriptionAccess =
|
||||||
activeSubscription != null &&
|
activeSubscription != null &&
|
||||||
(activeSubscription.features is List &&
|
activeSubscription.features.contains(
|
||||||
(activeSubscription.features as List).contains(
|
SubscriptionFeatureEnum.packs.name,
|
||||||
SubscriptionFeatureEnum.packs.name,
|
);
|
||||||
));
|
|
||||||
|
|
||||||
return _ok({
|
return _ok({
|
||||||
'packId': packId,
|
'packId': packId,
|
||||||
'isPurchased': isPurchased,
|
'isPurchased': hasDirectAccess,
|
||||||
'purchased': isPurchased,
|
'purchased': hasDirectAccess,
|
||||||
'hasSubscriptionAccess': hasSubscriptionAccess,
|
'hasSubscriptionAccess': hasSubscriptionAccess,
|
||||||
|
'isAvailable': isAvailable,
|
||||||
});
|
});
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
developer.log(
|
developer.log(
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,10 @@ class TelegramBotApiV2 {
|
||||||
if (userId != null && userId.isNotEmpty) {
|
if (userId != null && userId.isNotEmpty) {
|
||||||
// Get specific user info
|
// Get specific user info
|
||||||
final users = <UserModel>[];
|
final users = <UserModel>[];
|
||||||
final user = await _userRepository.getUserById(userId, includePacks: true);
|
final user = await _userRepository.getUserById(
|
||||||
|
userId,
|
||||||
|
includePacks: true,
|
||||||
|
);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
users.add(user);
|
users.add(user);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,10 @@ class UsersApiV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert UserData to UserDataDto
|
// Convert UserData to UserDataDto
|
||||||
final userDataModel = await _userRepository.getUserById(user.id!, withData: true);
|
final userDataModel = await _userRepository.getUserById(
|
||||||
|
user.id!,
|
||||||
|
withData: true,
|
||||||
|
);
|
||||||
if (userDataModel?.userData == null) {
|
if (userDataModel?.userData == null) {
|
||||||
return _json({'error': 'user_data_not_found'}, statusCode: 404);
|
return _json({'error': 'user_data_not_found'}, statusCode: 404);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:mnemo_cards_backend/packs/free_packs_distributor.dart';
|
import 'package:mnemo_cards_backend/packs/free_packs_distributor.dart';
|
||||||
|
import 'package:mnemo_cards_backend/packs/product_availability_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
||||||
|
|
||||||
import 'task.dart' as task;
|
import 'task.dart' as task;
|
||||||
|
|
@ -6,8 +7,13 @@ import 'task.dart' as task;
|
||||||
class AddFreePacks with task.Task {
|
class AddFreePacks with task.Task {
|
||||||
final FreePacksDistributor _freePacksDistributor;
|
final FreePacksDistributor _freePacksDistributor;
|
||||||
final UserRepository _userRepository;
|
final UserRepository _userRepository;
|
||||||
|
final ProductAvailabilityManager _productAvailabilityManager;
|
||||||
|
|
||||||
AddFreePacks(this._freePacksDistributor, this._userRepository);
|
AddFreePacks(
|
||||||
|
this._freePacksDistributor,
|
||||||
|
this._userRepository,
|
||||||
|
this._productAvailabilityManager,
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => 'add_free_packs';
|
String get name => 'add_free_packs';
|
||||||
|
|
@ -32,7 +38,10 @@ class AddFreePacks with task.Task {
|
||||||
for (final user in allUsers) {
|
for (final user in allUsers) {
|
||||||
if (user.id == null) continue;
|
if (user.id == null) continue;
|
||||||
final userPacks = await _userRepository.getUserPacks(user.id!);
|
final userPacks = await _userRepository.getUserPacks(user.id!);
|
||||||
final userPackIds = userPacks.map((p) => p.id).whereType<String>().toSet();
|
final userPackIds = userPacks
|
||||||
|
.map((p) => p.id)
|
||||||
|
.whereType<String>()
|
||||||
|
.toSet();
|
||||||
|
|
||||||
// Если у пользователя нет хотя бы одного из freePacks
|
// Если у пользователя нет хотя бы одного из freePacks
|
||||||
if (!freePackIds.every((packId) => userPackIds.contains(packId))) {
|
if (!freePackIds.every((packId) => userPackIds.contains(packId))) {
|
||||||
|
|
@ -49,7 +58,7 @@ class AddFreePacks with task.Task {
|
||||||
try {
|
try {
|
||||||
for (final pack in freePacks) {
|
for (final pack in freePacks) {
|
||||||
if (pack.id == null) continue;
|
if (pack.id == null) continue;
|
||||||
await _userRepository.grantPackAccess(
|
await _productAvailabilityManager.grantPackAccess(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
packId: pack.id!,
|
packId: pack.id!,
|
||||||
grantType: 'free',
|
grantType: 'free',
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,9 @@ class CheckAdminsTask with task.Task {
|
||||||
print('$name processing admin ID: $externalUserId');
|
print('$name processing admin ID: $externalUserId');
|
||||||
if (externalUserId.isNotEmpty) {
|
if (externalUserId.isNotEmpty) {
|
||||||
// Найти токен по externalUserId
|
// Найти токен по externalUserId
|
||||||
final rawUser = await _userRepository.getUserByExternalId(externalUserId);
|
final rawUser = await _userRepository.getUserByExternalId(
|
||||||
|
externalUserId,
|
||||||
|
);
|
||||||
final userId = rawUser?.id;
|
final userId = rawUser?.id;
|
||||||
|
|
||||||
if (userId == null) {
|
if (userId == null) {
|
||||||
|
|
|
||||||
|
|
@ -135,13 +135,18 @@ class PaymentDao extends DatabaseAccessor<AppDatabase>
|
||||||
|
|
||||||
/// Обновить статус платежа
|
/// Обновить статус платежа
|
||||||
Future<void> updatePaymentStatus(String externalToken, String status) async {
|
Future<void> updatePaymentStatus(String externalToken, String status) async {
|
||||||
print('🔍 PaymentDao.updatePaymentStatus: token=$externalToken, status=$status');
|
print(
|
||||||
final result = await (update(payments)..where((p) => p.externalToken.equals(externalToken))).write(
|
'🔍 PaymentDao.updatePaymentStatus: token=$externalToken, status=$status',
|
||||||
PaymentsCompanion(
|
|
||||||
status: Value(status),
|
|
||||||
updatedAt: Value(PgDateTime(DateTime.now())),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
final result =
|
||||||
|
await (update(
|
||||||
|
payments,
|
||||||
|
)..where((p) => p.externalToken.equals(externalToken))).write(
|
||||||
|
PaymentsCompanion(
|
||||||
|
status: Value(status),
|
||||||
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
||||||
|
),
|
||||||
|
);
|
||||||
print('✅ PaymentDao.updatePaymentStatus: result=$result');
|
print('✅ PaymentDao.updatePaymentStatus: result=$result');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -397,18 +397,11 @@ class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
|
||||||
|
|
||||||
/// Проверить, есть ли у пользователя доступ к паку
|
/// Проверить, есть ли у пользователя доступ к паку
|
||||||
Future<bool> hasPackAccess(String userId, String packId) async {
|
Future<bool> hasPackAccess(String userId, String packId) async {
|
||||||
print('🔍 UserDao.hasPackAccess: userId=$userId, packId=$packId');
|
|
||||||
print(
|
|
||||||
'🔍 UserDao.hasPackAccess: SQL: SELECT * FROM user_packs WHERE user_id = ? AND pack_id = ? LIMIT 1',
|
|
||||||
);
|
|
||||||
final query = select(userPacks)
|
final query = select(userPacks)
|
||||||
..where((up) => up.userId.equals(userId) & up.packId.equals(packId))
|
..where((up) => up.userId.equals(userId) & up.packId.equals(packId))
|
||||||
..limit(1);
|
..limit(1);
|
||||||
|
|
||||||
final results = await query.get();
|
final results = await query.get();
|
||||||
print(
|
|
||||||
'✅ UserDao.hasPackAccess: Result count=${results.length}, hasAccess=${results.isNotEmpty}',
|
|
||||||
);
|
|
||||||
return results.isNotEmpty;
|
return results.isNotEmpty;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import 'package:mnemo_cards_backend/cron/cron_executor.dart';
|
||||||
import 'package:mnemo_cards_backend/cron/generate_promocodes.dart';
|
import 'package:mnemo_cards_backend/cron/generate_promocodes.dart';
|
||||||
import 'package:mnemo_cards_backend/database/database.dart';
|
import 'package:mnemo_cards_backend/database/database.dart';
|
||||||
import 'package:mnemo_cards_backend/discounts/discounts_manager.dart';
|
import 'package:mnemo_cards_backend/discounts/discounts_manager.dart';
|
||||||
|
import 'package:mnemo_cards_backend/packs/product_availability_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_manager.dart';
|
import 'package:mnemo_cards_backend/user/user_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/tests/test_manager.dart';
|
import 'package:mnemo_cards_backend/tests/test_manager.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
||||||
|
|
@ -123,7 +124,11 @@ void main() async {
|
||||||
DeleteOldArchives(),
|
DeleteOldArchives(),
|
||||||
CheckAdminsTask(getIt.get<UserRepository>()),
|
CheckAdminsTask(getIt.get<UserRepository>()),
|
||||||
getIt.get<CheckPaymentTask>(),
|
getIt.get<CheckPaymentTask>(),
|
||||||
AddFreePacks(getIt.get<FreePacksDistributor>(), getIt.get<UserRepository>()),
|
AddFreePacks(
|
||||||
|
getIt.get<FreePacksDistributor>(),
|
||||||
|
getIt.get<UserRepository>(),
|
||||||
|
getIt.get<ProductAvailabilityManager>(),
|
||||||
|
),
|
||||||
Backup(backupDir),
|
Backup(backupDir),
|
||||||
GeneratePromocodes(database),
|
GeneratePromocodes(database),
|
||||||
DiscountCampaignTask(getIt.get<DiscountsManager>(), database),
|
DiscountCampaignTask(getIt.get<DiscountsManager>(), database),
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,6 @@ extension CardPackToDto on CardPack {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CardPackToModel on CardPack {
|
extension CardPackToModel on CardPack {
|
||||||
|
|
||||||
Future<CardPackModel> toModel() async {
|
Future<CardPackModel> toModel() async {
|
||||||
return CardPackModel(
|
return CardPackModel(
|
||||||
id: id,
|
id: id,
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:mnemo_cards_backend/user/user_repository.dart';
|
import 'package:mnemo_cards_backend/packs/product_availability_manager.dart';
|
||||||
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart';
|
||||||
import 'pack_repository.dart';
|
import 'pack_repository.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class FreePacksDistributor {
|
class FreePacksDistributor {
|
||||||
final UserRepository _userRepository;
|
final ProductAvailabilityManager _productAvailabilityManager;
|
||||||
final PackRepository _packRepository;
|
final PackRepository _packRepository;
|
||||||
|
|
||||||
FreePacksDistributor(this._userRepository, this._packRepository);
|
FreePacksDistributor(this._productAvailabilityManager, this._packRepository);
|
||||||
|
|
||||||
Future<List<CardPackModel>> getFreePacks() async {
|
Future<List<CardPackModel>> getFreePacks() async {
|
||||||
final allPacks = await _packRepository.getAllPacks(enabledOnly: true);
|
final allPacks = await _packRepository.getAllPacks(enabledOnly: true);
|
||||||
// Фильтруем паки с нулевой ценой или без цены
|
// Фильтруем паки с нулевой ценой или без цены
|
||||||
return allPacks
|
return allPacks.where((pack) {
|
||||||
.where((pack) {
|
final priceStr = pack.price;
|
||||||
final priceStr = pack.price;
|
if (priceStr == null || priceStr.isEmpty) return true;
|
||||||
if (priceStr == null || priceStr.isEmpty) return true;
|
final price = double.tryParse(priceStr);
|
||||||
final price = double.tryParse(priceStr);
|
return price == null || price == 0;
|
||||||
return price == null || price == 0;
|
}).toList();
|
||||||
})
|
|
||||||
.toList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> giveFreePacksToUser(UserModel user) async {
|
Future<void> giveFreePacksToUser(UserModel user) async {
|
||||||
|
|
@ -29,12 +27,15 @@ class FreePacksDistributor {
|
||||||
await givePacksToUser(user, packs);
|
await givePacksToUser(user, packs);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> givePacksToUser(UserModel user, List<CardPackModel> packs) async {
|
Future<void> givePacksToUser(
|
||||||
|
UserModel user,
|
||||||
|
List<CardPackModel> packs,
|
||||||
|
) async {
|
||||||
if (user.id == null) return;
|
if (user.id == null) return;
|
||||||
|
|
||||||
for (final pack in packs) {
|
for (final pack in packs) {
|
||||||
if (pack.id == null) continue;
|
if (pack.id == null) continue;
|
||||||
await _userRepository.grantPackAccess(
|
await _productAvailabilityManager.grantPackAccess(
|
||||||
userId: user.id!,
|
userId: user.id!,
|
||||||
packId: pack.id!,
|
packId: pack.id!,
|
||||||
grantType: 'free',
|
grantType: 'free',
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,9 @@ class PackDtoConverter {
|
||||||
CardPackModel model,
|
CardPackModel model,
|
||||||
UserModel? userModel,
|
UserModel? userModel,
|
||||||
) async {
|
) async {
|
||||||
// Check access via purchases list (source of truth is user_packs table in DB)
|
bool available =
|
||||||
// user.purchases is a cached list of pack IDs the user has access to
|
userModel != null &&
|
||||||
bool available = userModel != null &&
|
(userModel.packs.contains(model.id?.toString()) || userModel.admin);
|
||||||
(userModel.purchases.contains(model.id?.toString()) ||
|
|
||||||
userModel.admin);
|
|
||||||
if (userModel != null && !available) {
|
if (userModel != null && !available) {
|
||||||
available =
|
available =
|
||||||
userModel.subscriptionModel?.features.contains(
|
userModel.subscriptionModel?.features.contains(
|
||||||
|
|
|
||||||
|
|
@ -235,4 +235,3 @@ class PackRepository {
|
||||||
await _db.packDao.updateVoiceUrl(voiceId, voiceUrl);
|
await _db.packDao.updateVoiceUrl(voiceId, voiceUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -119,7 +119,9 @@ class UserManager {
|
||||||
String? name,
|
String? name,
|
||||||
}) async {
|
}) async {
|
||||||
// Check if user exists by externalId
|
// Check if user exists by externalId
|
||||||
var existingUserModel = await _userRepository.getUserByExternalId(externalId);
|
var existingUserModel = await _userRepository.getUserByExternalId(
|
||||||
|
externalId,
|
||||||
|
);
|
||||||
if (existingUserModel != null) {
|
if (existingUserModel != null) {
|
||||||
print('User found $name $email $telegram');
|
print('User found $name $email $telegram');
|
||||||
|
|
||||||
|
|
@ -134,7 +136,9 @@ class UserManager {
|
||||||
if (shouldUpdateEmail || shouldUpdateTelegram) {
|
if (shouldUpdateEmail || shouldUpdateTelegram) {
|
||||||
final updatedModel = existingUserModel.copyWith(
|
final updatedModel = existingUserModel.copyWith(
|
||||||
email: shouldUpdateEmail ? email : existingUserModel.email,
|
email: shouldUpdateEmail ? email : existingUserModel.email,
|
||||||
telegram: shouldUpdateTelegram ? telegram : existingUserModel.telegram,
|
telegram: shouldUpdateTelegram
|
||||||
|
? telegram
|
||||||
|
: existingUserModel.telegram,
|
||||||
);
|
);
|
||||||
await _userRepository.updateUser(updatedModel);
|
await _userRepository.updateUser(updatedModel);
|
||||||
existingUserModel = updatedModel;
|
existingUserModel = updatedModel;
|
||||||
|
|
@ -169,7 +173,10 @@ class UserManager {
|
||||||
userData: userDataCompanion,
|
userData: userDataCompanion,
|
||||||
);
|
);
|
||||||
|
|
||||||
final createdUserModel = await _userRepository.getUserById(userId, includePacks: true);
|
final createdUserModel = await _userRepository.getUserById(
|
||||||
|
userId,
|
||||||
|
includePacks: true,
|
||||||
|
);
|
||||||
if (createdUserModel == null) {
|
if (createdUserModel == null) {
|
||||||
throw Exception('Failed to create user');
|
throw Exception('Failed to create user');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ class UserRepository {
|
||||||
/// Получить пользователя по externalUserId
|
/// Получить пользователя по externalUserId
|
||||||
Future<UserModel?> getUserByExternalId(String externalId) async {
|
Future<UserModel?> getUserByExternalId(String externalId) async {
|
||||||
final user = await _db.userDao.getUserByExternalId(externalId);
|
final user = await _db.userDao.getUserByExternalId(externalId);
|
||||||
|
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
|
||||||
return await user.toUserModel();
|
return await user.toUserModel();
|
||||||
|
|
@ -141,29 +141,6 @@ class UserRepository {
|
||||||
return await Future.wait(packs.map((p) => p.toModel()));
|
return await Future.wait(packs.map((p) => p.toModel()));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Проверить, есть ли у пользователя доступ к паку
|
|
||||||
Future<bool> hasPackAccess(String userId, String packId) async {
|
|
||||||
return await _db.userDao.hasPackAccess(userId, packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Дать пользователю доступ к паку
|
|
||||||
Future<void> grantPackAccess({
|
|
||||||
required String userId,
|
|
||||||
required String packId,
|
|
||||||
String grantType = 'purchase',
|
|
||||||
}) async {
|
|
||||||
await _db.userDao.grantPackAccess(
|
|
||||||
userId: userId,
|
|
||||||
packId: packId,
|
|
||||||
grantType: grantType,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Отозвать доступ к паку
|
|
||||||
Future<void> revokePackAccess(String userId, String packId) async {
|
|
||||||
await _db.userDao.revokePackAccess(userId, packId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Получить UserData пользователя
|
/// Получить UserData пользователя
|
||||||
Future<UserData?> getUserData(String userId) async {
|
Future<UserData?> getUserData(String userId) async {
|
||||||
return await _db.userDao.getUserData(userId);
|
return await _db.userDao.getUserData(userId);
|
||||||
|
|
@ -179,4 +156,3 @@ class UserRepository {
|
||||||
await _db.userDao.updateLastOnline(userId);
|
await _db.userDao.updateLastOnline(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue