feat(bibliothèque): migration Drift v16, seed basket et seeding au démarrage (ticket #80)

Ajoute la migration Drift v16 (enum ExerciseCategory, champ isExample sur
exercises/programs/workout_templates, table local_seed_metadata), le
contenu seed basket déterministe (21 exercices, 1 programme, 1 séance-
modèle) dans lib/application/starter_content/, les ports
StarterSeedStateRepository/StarterContentRepository et le use case
SeedStarterContentUseCase branché dans AppBootstrap.create().

Validé GO par QA/Main : dart analyze propre, suite complète 167 tests
(4 échecs préexistants sans rapport avec ce lot), 4 nouveaux tests seed
tous verts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 07:29:49 +02:00
parent 75f52200a2
commit 24e266c0e9
12 changed files with 2052 additions and 16 deletions

View File

@ -18,6 +18,7 @@ part 'app_database.g.dart';
Exercises,
ExerciseImages,
ExerciseSteps,
LocalSeedMetadata,
MediaAssets,
OnlineAccountSessions,
PendingShareActions,
@ -47,7 +48,7 @@ final class AppDatabase extends _$AppDatabase {
}
@override
int get schemaVersion => 15;
int get schemaVersion => 16;
@override
MigrationStrategy get migration {
@ -55,6 +56,7 @@ final class AppDatabase extends _$AppDatabase {
onCreate: (migrator) async {
await migrator.createAll();
await _migrateToSchema15();
await _migrateToSchema16(migrator);
await _createIndexes();
},
onUpgrade: (migrator, from, to) async {
@ -108,6 +110,9 @@ final class AppDatabase extends _$AppDatabase {
if (from < 15) {
await _migrateToSchema15();
}
if (from < 16) {
await _migrateToSchema16(migrator);
}
await _createIndexes();
},
beforeOpen: (details) async {
@ -148,6 +153,22 @@ final class AppDatabase extends _$AppDatabase {
'CREATE INDEX IF NOT EXISTS idx_exercise_steps_exercise_id_position '
'ON exercise_steps (exercise_id, position)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_exercises_category '
'ON exercises (category)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_exercises_is_example '
'ON exercises (is_example)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_programs_is_example '
'ON programs (is_example)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_workout_templates_is_example '
'ON workout_templates (is_example)',
);
await customStatement(
'CREATE INDEX IF NOT EXISTS idx_online_account_sessions_logged_in '
'ON online_account_sessions (is_logged_in, updated_at)',
@ -637,6 +658,57 @@ CREATE TABLE IF NOT EXISTS active_set_timer_states (
}
}
Future<void> _migrateToSchema16(Migrator migrator) async {
await _addColumnIfMissing(
tableName: 'exercises',
columnName: 'category',
definition:
"category TEXT NOT NULL DEFAULT 'uncategorized' CHECK "
"(category IN ('shoot', 'freeThrows', 'dribble', 'finishing', "
"'conditioning', 'defense', 'mobility', 'uncategorized'))",
);
await _addColumnIfMissing(
tableName: 'exercises',
columnName: 'is_example',
definition:
'is_example INTEGER NOT NULL DEFAULT 0 '
'CHECK (is_example IN (0, 1))',
);
await _addColumnIfMissing(
tableName: 'programs',
columnName: 'is_example',
definition:
'is_example INTEGER NOT NULL DEFAULT 0 '
'CHECK (is_example IN (0, 1))',
);
await _addColumnIfMissing(
tableName: 'workout_templates',
columnName: 'is_example',
definition:
'is_example INTEGER NOT NULL DEFAULT 0 '
'CHECK (is_example IN (0, 1))',
);
await customStatement(
'CREATE TABLE IF NOT EXISTS local_seed_metadata ('
'key TEXT NOT NULL PRIMARY KEY, '
'version INTEGER NOT NULL CHECK (version >= 0), '
'applied_at INTEGER NOT NULL'
')',
);
}
Future<void> _addColumnIfMissing({
required String tableName,
required String columnName,
required String definition,
}) async {
final columns = await customSelect('PRAGMA table_info($tableName)').get();
final exists = columns.any((row) => row.data['name'] == columnName);
if (!exists) {
await customStatement('ALTER TABLE $tableName ADD COLUMN $definition');
}
}
Future<bool> _hasColumn(String tableName, String columnName) async {
final rows = await customSelect('PRAGMA table_info($tableName)').get();
return rows.any((row) => row.data['name'] == columnName);