mnemo_cards/mnemo_cards_backend/lib/user/user_manager_drift.dart
Dmitry 0f49280805
Some checks are pending
Backend CI / test (push) Waiting to run
Backend CI / build (push) Blocked by required conditions
Mobile App CI / test (push) Waiting to run
Mobile App CI / build-android (push) Blocked by required conditions
Mobile App CI / build-ios (push) Blocked by required conditions
Web App CI / test (push) Waiting to run
Web App 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
Deploy Telegram Bot / Deploy Telegram Bot (push) Waiting to run
fixes and format
2025-12-20 21:26:15 +03:00

179 lines
5.2 KiB
Dart

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<String?, DateTime> _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<UserModel?> 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<String> 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<UserModel?> 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<void> 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,
String? email,
String? telegram,
String? name,
}) async {
// Check if user exists by externalId
final existingUser = await _db.userDao.getUserByExternalId(externalId);
if (existingUser != null) {
print('User found $name $email $telegram');
final shouldUpdateEmail =
email != null && email.isNotEmpty && existingUser.email != email;
final shouldUpdateTelegram =
telegram != null &&
telegram.isNotEmpty &&
existingUser.telegram != telegram;
if (shouldUpdateEmail || shouldUpdateTelegram) {
await _db.userDao.updateUserPartial(
UsersCompanion(
id: drift.Value(existingUser.id),
email: shouldUpdateEmail
? drift.Value(email)
: const drift.Value.absent(),
telegram: shouldUpdateTelegram
? drift.Value(telegram)
: const drift.Value.absent(),
),
);
}
final userModel = await existingUser.toUserModel();
final token = await createOrGetAuthToken(userModel, externalId);
return (userModel, token);
}
print('Creating new user $name $email $telegram');
// Create new user
final userCompanion = UsersCompanion.insert(
externalUserId: externalId,
name: drift.Value(name),
email: drift.Value(email),
telegram: drift.Value(telegram),
);
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);
}
}