user
Some checks are pending
Backend CI / test (push) Waiting to run
Backend 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

This commit is contained in:
Dmitry 2025-12-14 03:13:49 +03:00
parent f66d66ae5c
commit 10f827534c
4 changed files with 76 additions and 38 deletions

View file

@ -219,15 +219,16 @@ class TasksSeederTask with cron_task.Task {
), ),
]; ];
// Convert UserTaskModel to UserTasksCompanion and insert // Convert UserTaskModel to UserTasksCompanion and insert через DAO
// PostgreSQL автоматически генерирует UUID через DEFAULT gen_random_uuid() // Используем предопределенные UUID для идемпотентности seeder'а
// Используем InsertMode.insertOrIgnore с предопределенными UUID для идемпотентности int insertedCount = 0;
int skippedCount = 0;
for (final taskData in tasksWithIds) { for (final taskData in tasksWithIds) {
final taskModel = taskData.task; final taskModel = taskData.task;
final rewardsJson = taskModel.rewards.map((r) => r.toJson()).toList(); final rewardsJson = taskModel.rewards.map((r) => r.toJson()).toList();
await _db.into(_db.userTasks).insert( final companion = UserTasksCompanion.insert(
UserTasksCompanion.insert(
id: Value(taskData.id), // Предопределенный UUID для идемпотентности seeder'а id: Value(taskData.id), // Предопределенный UUID для идемпотентности seeder'а
title: taskModel.title, title: taskModel.title,
description: taskModel.description, description: taskModel.description,
@ -242,12 +243,17 @@ class TasksSeederTask with cron_task.Task {
instructions: Value(taskModel.instructions), instructions: Value(taskModel.instructions),
tags: Value(taskModel.tags), tags: Value(taskModel.tags),
imageUrl: Value(taskModel.imageUrl), imageUrl: Value(taskModel.imageUrl),
),
mode: InsertMode.insertOrIgnore, // ON CONFLICT DO NOTHING
); );
final wasCreated = await _db.taskDao.createUserTaskOrIgnore(companion);
if (wasCreated) {
insertedCount++;
} else {
skippedCount++;
}
} }
print('Successfully seeded ${tasksWithIds.length} user tasks (duplicates ignored)'); print('Successfully seeded user tasks: $insertedCount inserted, $skippedCount skipped (already exist)');
} catch (e) { } catch (e) {
print('Error seeding tasks: $e'); print('Error seeding tasks: $e');
rethrow; rethrow;

View file

@ -83,6 +83,39 @@ class TaskDao extends DatabaseAccessor<AppDatabase> with _$TaskDaoMixin {
return inserted.id; return inserted.id;
} }
/// Создать задачу пользователя или игнорировать, если уже существует
/// Возвращает true, если задача была создана, false если уже существовала
///
/// Если task.id.present == true, проверяет существование перед вставкой
Future<bool> createUserTaskOrIgnore(UserTasksCompanion task) async {
// Если ID указан, проверяем существование перед вставкой
if (task.id.present) {
final existing = await getUserTaskById(task.id.value);
if (existing != null) {
return false; // Задача уже существует
}
}
try {
await into(db.userTasks).insert(
task,
mode: InsertMode.insertOrIgnore,
);
return true; // Задача была создана
} catch (e) {
// PostgreSQL error code 23505 = unique_violation (duplicate key)
// Это может произойти в редких случаях race condition
final errorString = e.toString();
if (errorString.contains('23505') ||
errorString.contains('duplicate key') ||
errorString.contains('unique constraint')) {
return false; // Задача уже существует
}
// Пробрасываем другие ошибки
rethrow;
}
}
/// Обновить задачу пользователя /// Обновить задачу пользователя
Future<bool> updateUserTask(UserTask task) { Future<bool> updateUserTask(UserTask task) {
return update(db.userTasks).replace(task); return update(db.userTasks).replace(task);

View file

@ -179,14 +179,6 @@ class AppDatabase extends _$AppDatabase {
final result = await customSelect('SELECT 1 as test').getSingle(); final result = await customSelect('SELECT 1 as test').getSingle();
print('Database connection successful: ${result.data}'); print('Database connection successful: ${result.data}');
// Удалить constraint valid_email если он существует
try {
await customStatement('ALTER TABLE users DROP CONSTRAINT IF EXISTS valid_email');
print('Removed valid_email constraint if it existed');
} catch (e) {
print('Warning: Could not remove valid_email constraint: $e');
}
// Включить foreign key constraints // Включить foreign key constraints
await customStatement('SET CONSTRAINTS ALL IMMEDIATE'); await customStatement('SET CONSTRAINTS ALL IMMEDIATE');
}, },

View file

@ -125,21 +125,28 @@ class UserManager {
print('Creating new user $name $email'); print('Creating new user $name $email');
// Create new user // Create new user with user data in transaction
final now = DateTime.now();
final userCompanion = UsersCompanion.insert( final userCompanion = UsersCompanion.insert(
externalUserId: externalId, externalUserId: externalId,
name: drift.Value(name), name: drift.Value(name),
email: drift.Value(email), email: drift.Value(email),
// Explicitly set fields with default values to ensure they're returned by insertReturning
admin: drift.Value(false),
purchases: drift.Value([]),
createdAt: drift.Value(PgDateTime(now)),
updatedAt: drift.Value(PgDateTime(now)),
isDeleted: drift.Value(false),
); );
final userId = await _db.userDao.createUser(userCompanion); final userDataCompanion = UserDatasCompanion.insert(
userId: '', // Will be set by createUserWithData via copyWith
registrationDate: drift.Value(PgDateTime(now)),
);
// Create user data final userId = await _db.userDao.createUserWithData(
await _db.userDao.createUserData( user: userCompanion,
UserDatasCompanion.insert( userData: userDataCompanion,
userId: userId,
registrationDate: drift.Value(PgDateTime(DateTime.now())),
),
); );
final user = await _db.userDao.getUserById(userId); final user = await _db.userDao.getUserById(userId);