This commit is contained in:
Dmitry 2025-11-28 00:48:01 +03:00
parent a807d0f90f
commit c678479137
16 changed files with 861 additions and 1544 deletions

View file

@ -315,6 +315,12 @@ jobs:
echo "🔄 Running pre-deploy version bump..." echo "🔄 Running pre-deploy version bump..."
./tools/deploy/bump-version.sh ./tools/deploy/bump-version.sh
- name: Push Version Bump
run: |
echo "📤 Pushing version bump to remote..."
git push
echo "✅ Version bump pushed"
- name: Configure Git - name: Configure Git
run: | run: |
git config --global http.postBuffer 52428800 git config --global http.postBuffer 52428800

View file

@ -1,10 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:developer';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:mnemo_cards_backend/api/authorize/helpers.dart'; import 'package:mnemo_cards_backend/api/authorize/helpers.dart';
import 'package:mnemo_cards_backend/api/v2/jwt_service.dart'; import 'package:mnemo_cards_backend/api/v2/jwt_service.dart';
import 'package:mnemo_cards_backend/api/user/google_api.dart'; import 'package:mnemo_cards_backend/api/user/google_api.dart';
import 'package:mnemo_cards_backend/auth/telegram_auth_code_service.dart'; import 'package:mnemo_cards_backend/auth/telegram_auth_code_service.dart';
import 'package:mnemo_cards_backend/user/telegram.dart';
import 'package:mnemo_cards_backend/user/user_manager.dart'; import 'package:mnemo_cards_backend/user/user_manager.dart';
import 'package:mnemo_cards_backend/user/user_model.dart'; import 'package:mnemo_cards_backend/user/user_model.dart';
import 'package:shelf/shelf.dart'; import 'package:shelf/shelf.dart';
@ -228,6 +230,48 @@ class AuthApiV2 {
/// POST /api/v2/auth/oauth/telegram /// POST /api/v2/auth/oauth/telegram
/// Authenticate with Telegram auth code from bot /// Authenticate with Telegram auth code from bot
/// Body: { code: string } /// Body: { code: string }
@Route.post('/auth/telegram/web-app')
@OpenApiRoute()
Future<Response> authenticateTelegramWebApp(Request request) async {
try {
final body = await request.readAsString();
final json = jsonDecode(body) as Map<String, dynamic>;
final initData = json['initData'] as String?;
if (initData == null || initData.isEmpty) {
return _badRequest('Telegram Web App init data is required');
}
// Use the existing telegram utils to get user ID
final telegramUtils = TelegramUtils();
final userId = await telegramUtils.getUserId(initData);
if (userId == null) {
return _badRequest('Invalid Telegram Web App data');
}
// Find or create user based on telegram user ID
var user = await _userManager.findByTelegramId(userId);
if (user == null) {
// Create new user if doesn't exist
user = await _userManager.createFromTelegramData(userId, initData);
}
// Generate tokens
final tokens = await _jwtService.generateTokens(user);
return _ok({
'user': await user.toDto(),
'accessToken': tokens.accessToken,
'refreshToken': tokens.refreshToken,
'expiresIn': tokens.expiresIn,
});
} catch (e, s) {
log('Telegram Web App auth error', error: e, stackTrace: s);
return _badRequest('Authentication failed: $e');
}
}
@Route.post('/auth/oauth/telegram') @Route.post('/auth/oauth/telegram')
@OpenApiRoute() @OpenApiRoute()
Future<Response> authenticateTelegram(Request request) async { Future<Response> authenticateTelegram(Request request) async {

View file

@ -33,6 +33,11 @@ Router _$AuthApiV2Router(AuthApiV2 service) {
r'/auth/telegram/code-status/<code>', r'/auth/telegram/code-status/<code>',
service.getTelegramCodeStatus, service.getTelegramCodeStatus,
); );
router.add(
'POST',
r'/auth/telegram/web-app',
service.authenticateTelegramWebApp,
);
router.add( router.add(
'POST', 'POST',
r'/auth/oauth/telegram', r'/auth/oauth/telegram',

View file

@ -1,4 +1,4 @@
platform :osx, '10.14' platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -259,7 +259,7 @@
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastSwiftUpdateCheck = 0920; LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 1430; LastUpgradeCheck = 1510;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
331C80D4294CF70F00263BE5 = { 331C80D4294CF70F00263BE5 = {
@ -553,7 +553,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
@ -632,7 +632,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx; SDKROOT = macosx;
@ -679,7 +679,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.14; MACOSX_DEPLOYMENT_TARGET = 10.15;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1430" LastUpgradeVersion = "1510"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@ -59,6 +59,7 @@
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal" debugServiceExtension = "internal"
enableGPUValidationMode = "1"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<BuildableProductRunnable <BuildableProductRunnable
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">

View file

@ -1,9 +1,13 @@
import Cocoa import Cocoa
import FlutterMacOS import FlutterMacOS
@NSApplicationMain @main
class AppDelegate: FlutterAppDelegate { class AppDelegate: FlutterAppDelegate {
override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
return true return true
} }
override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool {
return true
}
} }

File diff suppressed because it is too large Load diff

View file

@ -39,13 +39,25 @@ class AuthService {
// Get init data from Telegram Web App // Get init data from Telegram Web App
final webAppData = TelegramWebApp.instance; final webAppData = TelegramWebApp.instance;
final initData = webAppData.initData.toString(); String? initData;
if (initData.isEmpty) {
try {
// Try different ways to get init data
if (webAppData.initData != null) {
initData = webAppData.initData.toString();
} else if (webAppData.initDataUnsafe != null) {
initData = webAppData.initDataUnsafe.toString();
}
} catch (e) {
log('Error getting init data from Telegram Web App', error: e, name: 'AuthService');
}
if (initData == null || initData.isEmpty) {
log('No Telegram Web App init data available', name: 'AuthService'); log('No Telegram Web App init data available', name: 'AuthService');
return null; return null;
} }
log('Telegram Web App init data received, sending to backend', name: 'AuthService'); log('Telegram Web App init data received: $initData', name: 'AuthService');
// Authenticate with backend using Telegram Web App data // Authenticate with backend using Telegram Web App data
final authResponse = await _httpRepository.authenticateWithTelegramWebApp( final authResponse = await _httpRepository.authenticateWithTelegramWebApp(

View file

@ -413,12 +413,8 @@ class HttpRepositoryV2 {
final refreshToken = data['refreshToken'] as String; final refreshToken = data['refreshToken'] as String;
final expiresIn = data['expiresIn'] as int?; final expiresIn = data['expiresIn'] as int?;
// Save tokens await saveTokens(accessToken: accessToken, refreshToken: refreshToken, expiresIn: expiresIn,);
await saveTokens(
accessToken: accessToken,
refreshToken: refreshToken,
expiresIn: expiresIn,
);
final expiresAt = expiresIn != null final expiresAt = expiresIn != null
? DateTime.now().add(Duration(seconds: expiresIn)) ? DateTime.now().add(Duration(seconds: expiresIn))

View file

@ -64,8 +64,7 @@ class _AuthPageState extends State<AuthPage> {
// Notify router about auth change // Notify router about auth change
appScope.userScopeHolder.notifyAuthChanged(); appScope.userScopeHolder.notifyAuthChanged();
// Navigate to home // Router will automatically redirect to /home when authentication state changes
router.go('/home');
} else { } else {
setState(() { setState(() {
_errorMessage = 'Login cancelled'; _errorMessage = 'Login cancelled';
@ -86,9 +85,9 @@ class _AuthPageState extends State<AuthPage> {
} }
Future<void> _onTelegramLoginSuccess(BuildContext context) async { Future<void> _onTelegramLoginSuccess(BuildContext context) async {
// Get router before async operations to avoid context issues // Router will automatically redirect to /home when authentication state changes
final router = GoRouter.of(context); // No manual navigation needed - let the GoRouter handle the redirect
router.go('/home'); log('Telegram login successful - router will handle redirect to home', name: 'AuthPage');
} }

View file

@ -81,6 +81,7 @@ class _SignInWithTelegramState extends State<SignInWithTelegram> {
isClaimed: true, isClaimed: true,
expiresAt: DateTime.now(), expiresAt: DateTime.now(),
); );
_autoLoginAttempted = true; // Mark as attempted to prevent re-attempts
}); });
// Clear code field // Clear code field
@ -190,9 +191,16 @@ class _SignInWithTelegramState extends State<SignInWithTelegram> {
widget.onError('Срок действия кода истёк. Сгенерируйте новый.'); widget.onError('Срок действия кода истёк. Сгенерируйте новый.');
} else if (status.isConsumed) { } else if (status.isConsumed) {
_stopCodePolling(); _stopCodePolling();
} else if (status.isReadyForLogin && !_autoLoginAttempted && mounted) { } else if (status.isReadyForLogin && !_autoLoginAttempted && mounted) {
log('Auto-login triggered for code: ${status.code}', name: 'SignInWithTelegram');
_autoLoginAttempted = true; _autoLoginAttempted = true;
await _loginWithTelegram();
// Small delay to ensure UI updates are processed before navigation
await Future.delayed(const Duration(milliseconds: 100));
if (mounted) {
await _loginWithTelegram();
}
} }
} catch (e, s) { } catch (e, s) {
log( log(

View file

@ -1,6 +1,7 @@
import 'dart:developer'; import 'dart:developer';
import 'dart:math' as math; import 'dart:math' as math;
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart';
@ -438,7 +439,8 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
return Column( return Column(
children: [ children: [
// Модуль "проверка знаний" для мобильных // Модуль "проверка знаний" для мобильных
_buildMobileTestsSection(packColor), // Hide tests section for mobile devices for now
// _buildMobileTestsSection(packColor),
const SizedBox(height: 16), const SizedBox(height: 16),
@ -855,29 +857,19 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
final imageUrl = ApiConfigV2.getCardImageUrl(widget.packId, card.id); final imageUrl = ApiConfigV2.getCardImageUrl(widget.packId, card.id);
return Image.network( return CachedNetworkImage(
imageUrl, imageUrl: imageUrl,
fit: BoxFit.cover, fit: BoxFit.cover,
gaplessPlayback: true, maxWidthDiskCache: 2048,
loadingBuilder: (context, child, loadingProgress) { maxHeightDiskCache: 2048,
if (loadingProgress == null) { progressIndicatorBuilder: (context, child, loadingProgress) {
return child; return const Center(
} child: CircularProgressIndicator(
return Center( strokeWidth: 2,
child: SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
), ),
); );
}, },
errorBuilder: (context, error, stackTrace) { errorWidget: (context, url, error) {
return Center( return Center(
child: Icon( child: Icon(
Icons.broken_image, Icons.broken_image,
@ -886,6 +878,9 @@ class _PackDetailsPageState extends State<PackDetailsPage> {
), ),
); );
}, },
// Плавное появление загруженного изображения
fadeInDuration: const Duration(milliseconds: 100),
fadeOutDuration: const Duration(milliseconds: 100),
); );
} }

View file

@ -144,6 +144,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.12.0" version: "8.12.0"
cached_network_image:
dependency: "direct main"
description:
name: cached_network_image
sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
cached_network_image_platform_interface:
dependency: transitive
description:
name: cached_network_image_platform_interface
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
cached_network_image_web:
dependency: transitive
description:
name: cached_network_image_web
sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -501,6 +525,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_cache_manager:
dependency: transitive
description:
name: flutter_cache_manager
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
url: "https://pub.dev"
source: hosted
version: "3.4.1"
flutter_colorpicker: flutter_colorpicker:
dependency: transitive dependency: transitive
description: description:
@ -892,6 +924,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
octo_image:
dependency: transitive
description:
name: octo_image
sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
@ -1176,6 +1216,46 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.1" version: "1.10.1"
sqflite:
dependency: transitive
description:
name: sqflite
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
url: "https://pub.dev"
source: hosted
version: "2.4.2"
sqflite_android:
dependency: transitive
description:
name: sqflite_android
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
url: "https://pub.dev"
source: hosted
version: "2.4.2+2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
url: "https://pub.dev"
source: hosted
version: "2.5.6"
sqflite_darwin:
dependency: transitive
description:
name: sqflite_darwin
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
sqflite_platform_interface:
dependency: transitive
description:
name: sqflite_platform_interface
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -1216,6 +1296,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.4.1"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0
url: "https://pub.dev"
source: hosted
version: "3.4.0"
telegram_web_app: telegram_web_app:
dependency: "direct main" dependency: "direct main"
description: description:

View file

@ -60,6 +60,7 @@ dependencies:
shimmer: ^3.0.0 shimmer: ^3.0.0
auto_size_text: ^3.0.0 auto_size_text: ^3.0.0
fl_chart: ^0.68.0 fl_chart: ^0.68.0
cached_network_image: ^3.4.1
# Utils # Utils
universal_image: ^1.0.10 universal_image: ^1.0.10