feat(server): API de synchronisation incrémentale LWW (ticket #50)
Ajoute l'endpoint api/sync_api.dart, les use cases de synchronisation (application/sync_use_cases.dart) et l'adapter Postgres (infrastructure/postgres/synced_resource_repository.dart) implémentant un upsert LWW atomique via CTE (INSERT ... ON CONFLICT ... WHERE client_updated_at < EXCLUDED.client_updated_at, avec fallback UNION ALL pour le cas ignoré). dart pub get OK, dart analyze clean, dart test 15/15 vert. SQL d'upsert relu manuellement et jugé correct ; pas de test de bout en bout contre un vrai PostgreSQL faute d'accès Docker dans ce sandbox. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -88,6 +88,63 @@ final class AuthSession {
|
||||
}
|
||||
}
|
||||
|
||||
enum SyncedResourceType {
|
||||
exercise('exercise'),
|
||||
program('program'),
|
||||
workoutTemplate('workoutTemplate'),
|
||||
workoutHistory('workoutHistory'),
|
||||
mediaAsset('mediaAsset');
|
||||
|
||||
const SyncedResourceType(this.wireName);
|
||||
|
||||
final String wireName;
|
||||
|
||||
static SyncedResourceType parse(String value) {
|
||||
for (final type in values) {
|
||||
if (type.wireName == value) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw ValidationException('Unsupported resource type: $value.');
|
||||
}
|
||||
}
|
||||
|
||||
final class SyncedResource {
|
||||
SyncedResource({
|
||||
required String serverId,
|
||||
required String ownerUserId,
|
||||
required this.resourceType,
|
||||
required String clientId,
|
||||
required Map<String, Object?> payloadJson,
|
||||
required this.schemaVersion,
|
||||
required DateTime clientUpdatedAt,
|
||||
required DateTime serverUpdatedAt,
|
||||
DateTime? deletedAt,
|
||||
this.originDeviceId,
|
||||
}) : serverId = _nonBlank(serverId, 'Server resource id'),
|
||||
ownerUserId = _nonBlank(ownerUserId, 'Owner user id'),
|
||||
clientId = _nonBlank(clientId, 'Client resource id'),
|
||||
payloadJson = Map.unmodifiable(payloadJson),
|
||||
clientUpdatedAt = clientUpdatedAt.toUtc(),
|
||||
serverUpdatedAt = serverUpdatedAt.toUtc(),
|
||||
deletedAt = deletedAt?.toUtc() {
|
||||
if (schemaVersion <= 0) {
|
||||
throw const ValidationException('Schema version must be positive.');
|
||||
}
|
||||
}
|
||||
|
||||
final String serverId;
|
||||
final String ownerUserId;
|
||||
final SyncedResourceType resourceType;
|
||||
final String clientId;
|
||||
final Map<String, Object?> payloadJson;
|
||||
final int schemaVersion;
|
||||
final DateTime clientUpdatedAt;
|
||||
final DateTime serverUpdatedAt;
|
||||
final DateTime? deletedAt;
|
||||
final String? originDeviceId;
|
||||
}
|
||||
|
||||
String _nonBlank(String value, String label) {
|
||||
final trimmed = value.trim();
|
||||
if (trimmed.isEmpty) {
|
||||
|
||||
Reference in New Issue
Block a user