mnemo_cards/mnemo_cards_backend/lib/database/daos/user_dao.dart

351 lines
12 KiB
Dart
Raw Normal View History

2025-12-13 13:27:05 +00:00
import 'package:drift/drift.dart';
import '../database.dart';
import '../tables/users.dart';
import '../tables/auth.dart';
import '../tables/packs.dart';
import '../tables/relations.dart';
part 'user_dao.g.dart';
@DriftAccessor(tables: [Users, UserDatas, Tokens, RefreshTokens, UserPacks])
class UserDao extends DatabaseAccessor<AppDatabase> with _$UserDaoMixin {
UserDao(super.db);
// ==================== Users ====================
/// Получить пользователя по ID
2025-12-13 20:55:50 +00:00
Future<User?> getUserById(String id) {
2025-12-13 13:27:05 +00:00
return (select(users)..where((u) => u.id.equals(id))).getSingleOrNull();
}
/// Получить пользователя с UserData
2025-12-13 20:55:50 +00:00
Future<UserWithData?> getUserWithDataById(String id) async {
2025-12-13 13:27:05 +00:00
final query = select(users).join([
leftOuterJoin(userDatas, userDatas.userId.equalsExp(users.id)),
])..where(users.id.equals(id));
final result = await query.getSingleOrNull();
if (result == null) return null;
return UserWithData(
user: result.readTable(users),
userData: result.readTableOrNull(userDatas),
);
}
/// Получить пользователя по email
Future<User?> getUserByEmail(String email) {
return (select(users)..where((u) => u.email.equals(email))).getSingleOrNull();
}
/// Получить пользователя по externalUserId
Future<User?> getUserByExternalId(String externalId) {
return (select(users)
..where((u) => u.externalUserId.equals(externalId))
).getSingleOrNull();
}
/// Создать пользователя
2025-12-13 20:55:50 +00:00
Future<String> createUser(UsersCompanion user) async {
final inserted = await into(users).insertReturning(user);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Создать пользователя с UserData
2025-12-13 20:55:50 +00:00
Future<String> createUserWithData({
2025-12-13 13:27:05 +00:00
required UsersCompanion user,
required UserDatasCompanion userData,
}) async {
return await transaction(() async {
2025-12-13 20:55:50 +00:00
final inserted = await into(users).insertReturning(user);
final userId = inserted.id;
2025-12-13 13:27:05 +00:00
await into(userDatas).insert(userData.copyWith(userId: Value(userId)));
return userId;
});
}
/// Обновить пользователя
Future<bool> updateUser(User user) {
return update(users).replace(user);
}
/// Обновить пользователя частично
Future<void> updateUserPartial(UsersCompanion updates) {
final userId = updates.id.value;
if (userId == null) throw ArgumentError('User ID is required');
return (update(users)..where((u) => u.id.equals(userId))).write(updates);
}
/// Удалить пользователя (soft delete)
2025-12-13 20:55:50 +00:00
Future<void> softDeleteUser(String userId) {
2025-12-13 13:27:05 +00:00
return (update(users)..where((u) => u.id.equals(userId)))
.write(UsersCompanion(
isDeleted: const Value(true),
updatedAt: Value(DateTime.now()),
));
}
/// Получить всех пользователей (для админки)
Future<List<User>> getAllUsers({
int? limit,
int? offset,
bool includeDeleted = false,
}) {
final query = select(users);
if (!includeDeleted) {
query.where((u) => u.isDeleted.equals(false));
}
query.orderBy([(u) => OrderingTerm.desc(u.createdAt)]);
if (limit != null) {
query.limit(limit, offset: offset);
}
return query.get();
}
/// Подсчитать пользователей
Future<int> countUsers({bool includeDeleted = false}) async {
final countExpr = users.id.count();
final query = selectOnly(users)..addColumns([countExpr]);
if (!includeDeleted) {
query.where(users.isDeleted.equals(false));
}
return await query.map((row) => row.read(countExpr)!).getSingle();
}
/// Получить пользователей с активной подпиской
Stream<List<User>> watchUsersWithActiveSubscription() {
2025-12-13 14:48:00 +00:00
final now = DateTime.now();
final query = select(users).join([
innerJoin(
db.userSubscriptions,
db.userSubscriptions.userId.equalsExp(users.id) &
db.userSubscriptions.start.isSmallerThanValue(now) &
db.userSubscriptions.finish.isBiggerThanValue(now),
),
])..where(users.isDeleted.equals(false));
return query.watch().map((rows) =>
rows.map((row) => row.readTable(users)).toList()
);
2025-12-13 13:27:05 +00:00
}
// ==================== UserData ====================
/// Получить UserData пользователя
2025-12-13 20:55:50 +00:00
Future<UserData?> getUserData(String userId) {
2025-12-13 13:27:05 +00:00
return (select(userDatas)..where((ud) => ud.userId.equals(userId)))
.getSingleOrNull();
}
/// Создать UserData
2025-12-13 20:55:50 +00:00
Future<String> createUserData(UserDatasCompanion userData) async {
final inserted = await into(userDatas).insertReturning(userData);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Обновить UserData
Future<bool> updateUserData(UserData userData) {
return update(userDatas).replace(userData);
}
/// Обновить UserData частично
Future<void> updateUserDataPartial(UserDatasCompanion updates) {
final userId = updates.userId.value;
if (userId == null) throw ArgumentError('User ID is required');
return (update(userDatas)..where((ud) => ud.userId.equals(userId)))
.write(updates.copyWith(updatedAt: Value(DateTime.now())));
}
/// Обновить время последнего визита
2025-12-13 20:55:50 +00:00
Future<void> updateLastOnline(String userId) async {
2025-12-13 13:27:05 +00:00
await (update(userDatas)..where((ud) => ud.userId.equals(userId)))
.write(UserDatasCompanion(
lastTimeOnline: Value(DateTime.now()),
updatedAt: Value(DateTime.now()),
));
}
// ==================== Tokens ====================
/// Получить токен по значению
Future<Token?> getTokenByValue(String tokenValue) {
return (select(tokens)..where((t) => t.token.equals(tokenValue)))
.getSingleOrNull();
}
2025-12-13 14:48:00 +00:00
/// Получить токен по externalUserId
Future<Token?> getTokenByExternalUserId(String externalUserId) {
return (select(tokens)
..where((t) => t.externalUserId.equals(externalUserId))
..orderBy([(t) => OrderingTerm.desc(t.created)])
..limit(1)
).getSingleOrNull();
}
2025-12-13 13:27:05 +00:00
/// Получить токен пользователя
2025-12-13 20:55:50 +00:00
Future<Token?> getTokenByUserId(String userId) {
2025-12-13 13:27:05 +00:00
return (select(tokens)
..where((t) => t.userId.equals(userId))
..where((t) => t.expires.isBiggerThanValue(DateTime.now()))
..orderBy([(t) => OrderingTerm.desc(t.created)])
).getSingleOrNull();
}
/// Создать токен
2025-12-13 20:55:50 +00:00
Future<String> createToken(TokensCompanion token) async {
final inserted = await into(tokens).insertReturning(token);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Удалить токен
2025-12-13 20:55:50 +00:00
Future<int> deleteToken(String tokenId) {
2025-12-13 13:27:05 +00:00
return (delete(tokens)..where((t) => t.id.equals(tokenId))).go();
}
/// Удалить токен по значению
Future<int> deleteTokenByValue(String tokenValue) {
return (delete(tokens)..where((t) => t.token.equals(tokenValue))).go();
}
/// Удалить истекшие токены
Future<int> deleteExpiredTokens() {
return (delete(tokens)
..where((t) => t.expires.isSmallerThanValue(DateTime.now()))
).go();
}
// ==================== RefreshTokens ====================
/// Получить refresh token по JTI
Future<RefreshToken?> getRefreshTokenByJti(String jti) {
return (select(refreshTokens)..where((rt) => rt.jti.equals(jti)))
.getSingleOrNull();
}
/// Получить активные refresh токены пользователя
2025-12-13 20:55:50 +00:00
Future<List<RefreshToken>> getActiveRefreshTokens(String userId) {
2025-12-13 13:27:05 +00:00
return (select(refreshTokens)
..where((rt) => rt.userId.equals(userId))
..where((rt) => rt.isBlacklisted.equals(false))
..where((rt) => rt.expiresAt.isBiggerThanValue(DateTime.now()))
..orderBy([(rt) => OrderingTerm.desc(rt.createdAt)])
).get();
}
/// Создать refresh token
2025-12-13 20:55:50 +00:00
Future<String> createRefreshToken(RefreshTokensCompanion token) async {
final inserted = await into(refreshTokens).insertReturning(token);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Отозвать refresh token (blacklist)
2025-12-13 20:55:50 +00:00
Future<void> revokeRefreshToken(String tokenId) {
2025-12-13 13:27:05 +00:00
return (update(refreshTokens)..where((rt) => rt.id.equals(tokenId)))
.write(const RefreshTokensCompanion(
isBlacklisted: Value(true),
));
}
/// Отозвать refresh token по JTI
Future<void> revokeRefreshTokenByJti(String jti) {
return (update(refreshTokens)..where((rt) => rt.jti.equals(jti)))
.write(const RefreshTokensCompanion(
isBlacklisted: Value(true),
));
}
/// Удалить истекшие refresh токены
Future<int> deleteExpiredRefreshTokens() {
return (delete(refreshTokens)
..where((rt) => rt.expiresAt.isSmallerThanValue(DateTime.now()))
).go();
}
// ==================== TelegramAuthCodes ====================
/// Получить код авторизации
Future<TelegramAuthCode?> getAuthCode(String code) {
return (select(db.telegramAuthCodes)..where((ac) => ac.code.equals(code)))
.getSingleOrNull();
}
/// Создать код авторизации
2025-12-13 20:55:50 +00:00
Future<String> createAuthCode(TelegramAuthCodesCompanion code) async {
final inserted = await into(db.telegramAuthCodes).insertReturning(code);
return inserted.id;
2025-12-13 13:27:05 +00:00
}
/// Отметить код как использованный
Future<void> markAuthCodeAsUsed(String code) {
return (update(db.telegramAuthCodes)..where((ac) => ac.code.equals(code)))
.write(TelegramAuthCodesCompanion(
isUsed: const Value(true),
usedAt: Value(DateTime.now()),
));
}
/// Удалить истекшие коды
Future<int> deleteExpiredAuthCodes() {
return (delete(db.telegramAuthCodes)
..where((ac) => ac.expiresAt.isSmallerThanValue(DateTime.now()))
).go();
}
// ==================== User Packs ====================
/// Получить паки пользователя
2025-12-13 20:55:50 +00:00
Future<List<CardPack>> getUserPacks(String userId) async {
2025-12-13 13:27:05 +00:00
final query = select(cardPacks).join([
innerJoin(
userPacks,
userPacks.packId.equalsExp(cardPacks.id) &
userPacks.userId.equals(userId),
),
]);
return query.map((row) => row.readTable(cardPacks)).get();
}
/// Проверить, есть ли у пользователя доступ к паку
2025-12-13 20:55:50 +00:00
Future<bool> hasPackAccess(String userId, String packId) async {
2025-12-13 13:27:05 +00:00
final query = select(userPacks)
..where((up) => up.userId.equals(userId) & up.packId.equals(packId));
final result = await query.getSingleOrNull();
return result != null;
}
/// Дать пользователю доступ к паку
Future<void> grantPackAccess({
2025-12-13 20:55:50 +00:00
required String userId,
required String packId,
2025-12-13 13:27:05 +00:00
String grantType = 'purchase',
}) async {
await into(userPacks).insert(
UserPacksCompanion.insert(
userId: userId,
packId: packId,
grantType: Value(grantType),
),
mode: InsertMode.insertOrIgnore, // игнорировать если уже есть
);
}
/// Отозвать доступ к паку
2025-12-13 20:55:50 +00:00
Future<void> revokePackAccess(String userId, String packId) async {
2025-12-13 13:27:05 +00:00
await (delete(userPacks)
..where((up) => up.userId.equals(userId) & up.packId.equals(packId))
).go();
}
}
/// Вспомогательный класс для User с UserData
class UserWithData {
final User user;
final UserData? userData;
UserWithData({required this.user, this.userData});
}