2025-12-13 13:27:05 +00:00
|
|
|
|
import 'package:drift/drift.dart';
|
2025-12-13 23:35:14 +00:00
|
|
|
|
import 'package:drift_postgres/drift_postgres.dart';
|
2025-12-13 13:27:05 +00:00
|
|
|
|
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),
|
2025-12-13 23:35:14 +00:00
|
|
|
|
updatedAt: Value(PgDateTime(DateTime.now())),
|
2025-12-13 13:27:05 +00:00
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Получить всех пользователей (для админки)
|
|
|
|
|
|
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 23:35:14 +00:00
|
|
|
|
final now = PgDateTime(DateTime.now());
|
2025-12-13 14:48:00 +00:00
|
|
|
|
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)))
|
2025-12-13 23:35:14 +00:00
|
|
|
|
.write(updates.copyWith(updatedAt: Value(PgDateTime(DateTime.now()))));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Обновить время последнего визита
|
2025-12-13 20:55:50 +00:00
|
|
|
|
Future<void> updateLastOnline(String userId) async {
|
2025-12-13 23:35:14 +00:00
|
|
|
|
final now = PgDateTime(DateTime.now());
|
2025-12-13 13:27:05 +00:00
|
|
|
|
await (update(userDatas)..where((ud) => ud.userId.equals(userId)))
|
|
|
|
|
|
.write(UserDatasCompanion(
|
2025-12-13 23:35:14 +00:00
|
|
|
|
lastTimeOnline: Value(now),
|
|
|
|
|
|
updatedAt: Value(now),
|
2025-12-13 13:27:05 +00:00
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== Tokens ====================
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Получить токен по значению (только активные)
|
2025-12-13 13:27:05 +00:00
|
|
|
|
Future<Token?> getTokenByValue(String tokenValue) {
|
2025-12-14 20:42:38 +00:00
|
|
|
|
return (select(tokens)
|
|
|
|
|
|
..where((t) => t.token.equals(tokenValue) & t.isDeleted.equals(false))
|
|
|
|
|
|
).getSingleOrNull();
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Получить токен по externalUserId (только активные)
|
2025-12-13 14:48:00 +00:00
|
|
|
|
Future<Token?> getTokenByExternalUserId(String externalUserId) {
|
|
|
|
|
|
return (select(tokens)
|
2025-12-14 20:42:38 +00:00
|
|
|
|
..where((t) => t.externalUserId.equals(externalUserId) & t.isDeleted.equals(false))
|
2025-12-13 14:48:00 +00:00
|
|
|
|
..orderBy([(t) => OrderingTerm.desc(t.created)])
|
|
|
|
|
|
..limit(1)
|
|
|
|
|
|
).getSingleOrNull();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +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)
|
2025-12-14 20:42:38 +00:00
|
|
|
|
..where((t) => t.userId.equals(userId) & t.isDeleted.equals(false))
|
2025-12-13 23:35:14 +00:00
|
|
|
|
..where((t) => t.expires.isBiggerThanValue(PgDateTime(DateTime.now())))
|
2025-12-13 13:27:05 +00:00
|
|
|
|
..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-14 20:42:38 +00:00
|
|
|
|
/// Удалить токен (soft delete)
|
|
|
|
|
|
Future<int> softDeleteToken(String tokenId) {
|
|
|
|
|
|
return (update(tokens)..where((t) => t.id.equals(tokenId)))
|
|
|
|
|
|
.write(TokensCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(DateTime.now()),
|
|
|
|
|
|
));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Удалить токен по значению (soft delete)
|
|
|
|
|
|
Future<int> softDeleteTokenByValue(String tokenValue) {
|
|
|
|
|
|
return (update(tokens)..where((t) => t.token.equals(tokenValue)))
|
|
|
|
|
|
.write(TokensCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(DateTime.now()),
|
|
|
|
|
|
));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Удалить истекшие токены (soft delete)
|
|
|
|
|
|
Future<int> softDeleteExpiredTokens() {
|
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
|
return (update(tokens)
|
|
|
|
|
|
..where((t) => t.expires.isSmallerThanValue(PgDateTime(now)) & t.isDeleted.equals(false))
|
|
|
|
|
|
).write(TokensCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(now),
|
|
|
|
|
|
));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== RefreshTokens ====================
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Получить refresh token по JTI (только активные)
|
2025-12-13 13:27:05 +00:00
|
|
|
|
Future<RefreshToken?> getRefreshTokenByJti(String jti) {
|
2025-12-14 20:42:38 +00:00
|
|
|
|
return (select(refreshTokens)
|
|
|
|
|
|
..where((rt) => rt.jti.equals(jti) & rt.isDeleted.equals(false))
|
|
|
|
|
|
).getSingleOrNull();
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Получить активные 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)
|
2025-12-14 20:42:38 +00:00
|
|
|
|
..where((rt) => rt.userId.equals(userId) & rt.isDeleted.equals(false))
|
2025-12-13 13:27:05 +00:00
|
|
|
|
..where((rt) => rt.isBlacklisted.equals(false))
|
2025-12-13 23:35:14 +00:00
|
|
|
|
..where((rt) => rt.expiresAt.isBiggerThanValue(PgDateTime(DateTime.now())))
|
2025-12-13 13:27:05 +00:00
|
|
|
|
..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),
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Удалить истекшие refresh токены (soft delete)
|
|
|
|
|
|
Future<int> softDeleteExpiredRefreshTokens() {
|
|
|
|
|
|
final now = DateTime.now();
|
|
|
|
|
|
return (update(refreshTokens)
|
|
|
|
|
|
..where((rt) => rt.expiresAt.isSmallerThanValue(PgDateTime(now)) & rt.isDeleted.equals(false))
|
|
|
|
|
|
).write(RefreshTokensCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(now),
|
|
|
|
|
|
));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== TelegramAuthCodes ====================
|
|
|
|
|
|
|
2025-12-14 20:42:38 +00:00
|
|
|
|
/// Получить код авторизации (только активные)
|
2025-12-13 13:27:05 +00:00
|
|
|
|
Future<TelegramAuthCode?> getAuthCode(String code) {
|
2025-12-14 20:42:38 +00:00
|
|
|
|
return (select(db.telegramAuthCodes)
|
|
|
|
|
|
..where((ac) => ac.code.equals(code) & ac.isDeleted.equals(false))
|
|
|
|
|
|
).getSingleOrNull();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Удалить код авторизации (soft delete)
|
|
|
|
|
|
Future<void> softDeleteAuthCode(String codeId) {
|
|
|
|
|
|
return (update(db.telegramAuthCodes)..where((ac) => ac.id.equals(codeId)))
|
|
|
|
|
|
.write(TelegramAuthCodesCompanion(
|
|
|
|
|
|
isDeleted: const Value(true),
|
|
|
|
|
|
deletedAt: Value(DateTime.now()),
|
|
|
|
|
|
));
|
2025-12-13 13:27:05 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Создать код авторизации
|
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),
|
2025-12-13 23:35:14 +00:00
|
|
|
|
usedAt: Value(PgDateTime(DateTime.now())),
|
2025-12-13 13:27:05 +00:00
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Удалить истекшие коды
|
|
|
|
|
|
Future<int> deleteExpiredAuthCodes() {
|
|
|
|
|
|
return (delete(db.telegramAuthCodes)
|
2025-12-13 23:35:14 +00:00
|
|
|
|
..where((ac) => ac.expiresAt.isSmallerThanValue(PgDateTime(DateTime.now())))
|
2025-12-13 13:27:05 +00:00
|
|
|
|
).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});
|
|
|
|
|
|
}
|