import 'package:drift_postgres/drift_postgres.dart'; import 'package:injectable/injectable.dart'; import 'package:drift/drift.dart' as drift; import 'package:mnemo_cards_backend/database/database.dart'; import 'package:mnemo_cards_common_backend/mnemo_cards_common_backend.dart'; import 'package:mnemo_cards_common/mnemo_cards_common.dart'; import '../packs/free_packs_distributor.dart'; import '../statistics/session_tracker.dart'; import '../statistics/statistics_calculator.dart'; import '../statistics/achievement_manager.dart'; import 'secure.dart'; import 'user_drift_extension.dart'; Map _onlineUsers = {}; @lazySingleton class UserManager { final AppDatabase _db; final FreePacksDistributor _freePacksDistributor; final SessionTracker _sessionTracker; final StatisticsCalculator _statisticsCalculator; final AchievementManager _achievementManager; UserManager( this._db, this._freePacksDistributor, this._sessionTracker, this._statisticsCalculator, this._achievementManager, ); Future fetchUser(String id) async { final user = await _db.userDao.getUserById(id); if (user == null) return null; return await user.toUserModel(); } DateTime? lastOnline(String id) => _onlineUsers[id]; Future createOrGetAuthToken(UserModel user, String externalId) async { if (user.id == null) { throw Exception('Cant create token for empty id'); } final now = DateTime.now(); final token = await _db.userDao.getTokenByUserId(user.id!); if (token != null) { if (token.expires.dateTime.isAfter(now)) { return token.token; } await _db.userDao.deleteToken(token.id); } final userToken = Secure.token(); await _db.userDao.createToken( TokensCompanion.insert( token: userToken, externalUserId: externalId, userId: user.id!, expires: PgDateTime(now.add(const Duration(days: 360))), ), ); return userToken; } Future getUserByToken(String authToken) async { final token = await _db.userDao.getTokenByValue(authToken); if (token == null) { return null; } final now = DateTime.now(); if (token.expires.dateTime.isBefore(now)) { await _db.userDao.deleteToken(token.id); return null; } final user = await fetchUser(token.userId); if (user != null) { _onlineUsers[token.userId] = now; if (_onlineUsers.length > 300) { updateOnlineUsers(); } } return user; } Future updateOnlineUsers() async { if (_onlineUsers.isEmpty) { return; } final userIds = _onlineUsers.keys.whereNotNull().toList(); for (final userId in userIds) { final lastOnline = _onlineUsers[userId]; if (lastOnline != null) { await _db.userDao.updateUserDataPartial( UserDatasCompanion( userId: drift.Value(userId), lastTimeOnline: drift.Value(PgDateTime(lastOnline)), updatedAt: drift.Value(PgDateTime(DateTime.now())), ), ); } } _onlineUsers.clear(); } Future<(UserModel, String)> createOrGetUser({ required String externalId, required String email, String? name, }) async { // Check if user exists by externalId final existingUser = await _db.userDao.getUserByExternalId(externalId); if (existingUser != null) { print('User found $name $email'); final userModel = await existingUser.toUserModel(); final token = await createOrGetAuthToken(userModel, externalId); return (userModel, token); } print('Creating new user $name $email'); // Create new user final userCompanion = UsersCompanion.insert( externalUserId: externalId, name: drift.Value(name), email: drift.Value(email), ); final userId = await _db.userDao.createUser(userCompanion); // Create user data await _db.userDao.createUserData( UserDatasCompanion.insert( userId: userId, registrationDate: drift.Value(PgDateTime(DateTime.now())), ), ); final user = await _db.userDao.getUserById(userId); if (user == null) { throw Exception('Failed to create user'); } final userModel = await user.toUserModel(); final token = await createOrGetAuthToken(userModel, externalId); // Give free packs to new user await _freePacksDistributor.giveFreePacksToUser(userModel); print('User $name $email created successfully'); return (userModel, token); } }