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

@ -2,9 +2,76 @@ import 'dart:convert';
import '../domain/domain.dart';
import 'ports.dart';
import 'starter_content/basket_starter_seed_v1.dart';
import 'starter_content/starter_content.dart';
const Object _useCaseUnchanged = Object();
enum StarterSeedStatus { inserted, skippedAlreadyApplied, skippedNotEmpty }
final class StarterSeedResult {
const StarterSeedResult({required this.status, required this.appliedVersion});
final StarterSeedStatus status;
final int appliedVersion;
}
final class SeedStarterContentUseCase {
const SeedStarterContentUseCase({
required this.seedStateRepository,
required this.contentRepository,
required this.clock,
required this.originDeviceId,
});
final StarterSeedStateRepository seedStateRepository;
final StarterContentRepository contentRepository;
final Clock clock;
final String originDeviceId;
Future<StarterSeedResult> run() async {
final appliedVersion = await seedStateRepository
.readAppliedStarterSeedVersion();
if (appliedVersion >= starterSeedVersion) {
return const StarterSeedResult(
status: StarterSeedStatus.skippedAlreadyApplied,
appliedVersion: starterSeedVersion,
);
}
final now = clock.now();
final isEmpty = await contentRepository.isLocalContentEmpty();
if (!isEmpty) {
await seedStateRepository.writeAppliedStarterSeedVersion(
starterSeedVersion,
now,
);
return const StarterSeedResult(
status: StarterSeedStatus.skippedNotEmpty,
appliedVersion: starterSeedVersion,
);
}
await contentRepository.insertStarterContent(
buildStarterContent(
exerciseSeeds: basketStarterExerciseSeedsV1,
programSeed: basketStarterProgramSeedV1,
templateSeed: basketStarterWorkoutTemplateSeedV1,
now: now,
originDeviceId: originDeviceId,
),
);
await seedStateRepository.writeAppliedStarterSeedVersion(
starterSeedVersion,
now,
);
return const StarterSeedResult(
status: StarterSeedStatus.inserted,
appliedVersion: starterSeedVersion,
);
}
}
final class AuthUseCases {
const AuthUseCases({
required this.tokenStore,