feat(online): partage client - use cases, inbox cache et import local (ticket #66)
Ajoute l'adapter API distant de partage (infrastructure/remote/share_api.dart) et les use cases associés (application/use_cases.dart), avec cache d'inbox et import local des ressources partagées acceptées. Étend le modèle Drift (migration schemaVersion 11→12) et les entités du domaine en conséquence. flutter pub get OK, build_runner OK, dart format appliqué, analyze propre (mêmes infos préexistantes), 124/124 tests verts, build APK debug validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -19,9 +19,11 @@ part 'app_database.g.dart';
|
||||
ExerciseSteps,
|
||||
MediaAssets,
|
||||
OnlineAccountSessions,
|
||||
PendingShareActions,
|
||||
ProgramExercises,
|
||||
Programs,
|
||||
RemoteResourceMappings,
|
||||
ShareInboxItems,
|
||||
SyncMetadataEntries,
|
||||
WorkoutHistories,
|
||||
WorkoutHistorySetResults,
|
||||
@ -44,7 +46,7 @@ final class AppDatabase extends _$AppDatabase {
|
||||
}
|
||||
|
||||
@override
|
||||
int get schemaVersion => 11;
|
||||
int get schemaVersion => 12;
|
||||
|
||||
@override
|
||||
MigrationStrategy get migration {
|
||||
@ -92,6 +94,9 @@ final class AppDatabase extends _$AppDatabase {
|
||||
if (from < 11) {
|
||||
await _migrateToSchema11(migrator);
|
||||
}
|
||||
if (from < 12) {
|
||||
await _migrateToSchema12(migrator);
|
||||
}
|
||||
await _createIndexes();
|
||||
},
|
||||
beforeOpen: (details) async {
|
||||
@ -140,6 +145,14 @@ final class AppDatabase extends _$AppDatabase {
|
||||
'CREATE INDEX IF NOT EXISTS idx_remote_resource_mappings_resource '
|
||||
'ON remote_resource_mappings (resource_type, client_id)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_share_inbox_items_created_at '
|
||||
'ON share_inbox_items (created_at)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_pending_share_actions_status '
|
||||
'ON pending_share_actions (status, created_at)',
|
||||
);
|
||||
await customStatement(
|
||||
'CREATE INDEX IF NOT EXISTS idx_workout_template_programs_template_id '
|
||||
'ON workout_template_programs (workout_template_id)',
|
||||
@ -341,4 +354,9 @@ extension on AppDatabase {
|
||||
await migrator.createTable(syncMetadataEntries);
|
||||
await migrator.createTable(remoteResourceMappings);
|
||||
}
|
||||
|
||||
Future<void> _migrateToSchema12(Migrator migrator) async {
|
||||
await migrator.createTable(shareInboxItems);
|
||||
await migrator.createTable(pendingShareActions);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -395,7 +395,40 @@ final class DriftLocalSyncChangeRepository
|
||||
);
|
||||
return true;
|
||||
case SyncResourceType.program:
|
||||
final program = _programFromPayload(item);
|
||||
await database.transaction(() async {
|
||||
await database
|
||||
.into(database.programs)
|
||||
.insertOnConflictUpdate(_programCompanion(program));
|
||||
for (final exercise in program.exercises) {
|
||||
await database
|
||||
.into(database.programExercises)
|
||||
.insertOnConflictUpdate(_programExerciseCompanion(exercise));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
case SyncResourceType.workoutTemplate:
|
||||
final template = _workoutTemplateFromPayload(item);
|
||||
await database.transaction(() async {
|
||||
await database
|
||||
.into(database.workoutTemplates)
|
||||
.insertOnConflictUpdate(_workoutTemplateCompanion(template));
|
||||
for (final program in template.programs) {
|
||||
await database
|
||||
.into(database.workoutTemplatePrograms)
|
||||
.insertOnConflictUpdate(
|
||||
_workoutTemplateProgramCompanion(program),
|
||||
);
|
||||
}
|
||||
for (final override in template.overrides) {
|
||||
await database
|
||||
.into(database.workoutTemplateExerciseOverrides)
|
||||
.insertOnConflictUpdate(
|
||||
_workoutTemplateExerciseOverrideCompanion(override),
|
||||
);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
case SyncResourceType.workoutHistory:
|
||||
return false;
|
||||
}
|
||||
@ -483,6 +516,132 @@ final class DriftLocalSyncChangeRepository
|
||||
}
|
||||
}
|
||||
|
||||
final class DriftShareInboxRepository implements ShareInboxRepository {
|
||||
const DriftShareInboxRepository(this.database);
|
||||
|
||||
final db.AppDatabase database;
|
||||
|
||||
@override
|
||||
Future<List<domain.ShareInboxItem>> listAll() async {
|
||||
final rows = await (database.select(
|
||||
database.shareInboxItems,
|
||||
)..orderBy([(table) => OrderingTerm.desc(table.createdAt)])).get();
|
||||
return rows.map(_shareInboxItemFromRow).toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<domain.ShareInboxItem?> findByShareId(String shareId) async {
|
||||
final row = await (database.select(
|
||||
database.shareInboxItems,
|
||||
)..where((table) => table.shareId.equals(shareId))).getSingleOrNull();
|
||||
return row == null ? null : _shareInboxItemFromRow(row);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upsert(domain.ShareInboxItem item) async {
|
||||
await database
|
||||
.into(database.shareInboxItems)
|
||||
.insertOnConflictUpdate(_shareInboxItemCompanion(item));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> upsertAll(List<domain.ShareInboxItem> items) async {
|
||||
await database.batch((batch) {
|
||||
batch.insertAllOnConflictUpdate(
|
||||
database.shareInboxItems,
|
||||
items.map(_shareInboxItemCompanion).toList(),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> markStatus(
|
||||
String shareId,
|
||||
domain.ShareInboxStatus status,
|
||||
DateTime respondedAt,
|
||||
) async {
|
||||
await (database.update(
|
||||
database.shareInboxItems,
|
||||
)..where((table) => table.shareId.equals(shareId))).write(
|
||||
db.ShareInboxItemsCompanion(
|
||||
status: Value<String>(_shareInboxStatusToDb(status)),
|
||||
respondedAt: Value<DateTime?>(respondedAt.toUtc()),
|
||||
updatedAt: Value<DateTime>(respondedAt.toUtc()),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class DriftPendingShareActionRepository
|
||||
implements PendingShareActionRepository {
|
||||
const DriftPendingShareActionRepository(this.database);
|
||||
|
||||
final db.AppDatabase database;
|
||||
|
||||
@override
|
||||
Future<List<domain.PendingShareAction>> listPending() async {
|
||||
final rows =
|
||||
await (database.select(database.pendingShareActions)
|
||||
..where(
|
||||
(table) => table.status.isIn([
|
||||
_pendingShareActionStatusToDb(
|
||||
domain.PendingShareActionStatus.pending,
|
||||
),
|
||||
_pendingShareActionStatusToDb(
|
||||
domain.PendingShareActionStatus.failed,
|
||||
),
|
||||
]),
|
||||
)
|
||||
..orderBy([(table) => OrderingTerm.asc(table.createdAt)]))
|
||||
.get();
|
||||
return rows.map(_pendingShareActionFromRow).toList(growable: false);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> add(domain.PendingShareAction action) async {
|
||||
await database
|
||||
.into(database.pendingShareActions)
|
||||
.insertOnConflictUpdate(_pendingShareActionCompanion(action));
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> markSucceeded(String id, DateTime attemptedAt) async {
|
||||
await _markActionAttempt(
|
||||
id,
|
||||
attemptedAt,
|
||||
domain.PendingShareActionStatus.succeeded,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> markFailed(String id, DateTime attemptedAt) async {
|
||||
await _markActionAttempt(
|
||||
id,
|
||||
attemptedAt,
|
||||
domain.PendingShareActionStatus.failed,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _markActionAttempt(
|
||||
String id,
|
||||
DateTime attemptedAt,
|
||||
domain.PendingShareActionStatus status,
|
||||
) async {
|
||||
final row = await (database.select(
|
||||
database.pendingShareActions,
|
||||
)..where((table) => table.id.equals(id))).getSingleOrNull();
|
||||
await (database.update(
|
||||
database.pendingShareActions,
|
||||
)..where((table) => table.id.equals(id))).write(
|
||||
db.PendingShareActionsCompanion(
|
||||
lastAttemptAt: Value<DateTime?>(attemptedAt.toUtc()),
|
||||
attemptCount: Value<int>((row?.attemptCount ?? 0) + 1),
|
||||
status: Value<String>(_pendingShareActionStatusToDb(status)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final class DriftProgramRepository implements ProgramRepository {
|
||||
const DriftProgramRepository(this.database);
|
||||
|
||||
@ -1846,6 +2005,74 @@ domain.UserAccountSession _userAccountSessionFromRow(
|
||||
);
|
||||
}
|
||||
|
||||
db.ShareInboxItemsCompanion _shareInboxItemCompanion(
|
||||
domain.ShareInboxItem item,
|
||||
) {
|
||||
final updatedAt = item.respondedAt ?? item.createdAt;
|
||||
return db.ShareInboxItemsCompanion.insert(
|
||||
shareId: item.shareId,
|
||||
senderUserId: item.senderUserId,
|
||||
resourceType: _shareResourceTypeToDb(item.resourceType),
|
||||
payloadJson: item.payloadJson,
|
||||
status: _shareInboxStatusToDb(item.status),
|
||||
createdAt: item.createdAt.toUtc(),
|
||||
updatedAt: updatedAt.toUtc(),
|
||||
respondedAt: Value<DateTime?>(_utcOrNull(item.respondedAt)),
|
||||
);
|
||||
}
|
||||
|
||||
domain.ShareInboxItem _shareInboxItemFromRow(db.ShareInboxItem row) {
|
||||
return domain.ShareInboxItem(
|
||||
shareId: row.shareId,
|
||||
senderUserId: row.senderUserId,
|
||||
resourceType: _shareResourceTypeFromDb(row.resourceType),
|
||||
payloadJson: row.payloadJson,
|
||||
status: _shareInboxStatusFromDb(row.status),
|
||||
createdAt: _utc(row.createdAt),
|
||||
respondedAt: _utcOrNull(row.respondedAt),
|
||||
);
|
||||
}
|
||||
|
||||
db.PendingShareActionsCompanion _pendingShareActionCompanion(
|
||||
domain.PendingShareAction action,
|
||||
) {
|
||||
return db.PendingShareActionsCompanion.insert(
|
||||
id: action.id,
|
||||
actionType: _pendingShareActionTypeToDb(action.actionType),
|
||||
shareId: Value<String?>(action.shareId),
|
||||
resourceType: Value<String?>(
|
||||
action.resourceType == null
|
||||
? null
|
||||
: _shareResourceTypeToDb(action.resourceType!),
|
||||
),
|
||||
payloadJson: Value<String?>(action.payloadJson),
|
||||
recipientEmailsJson: Value<String?>(action.recipientEmailsJson),
|
||||
createdAt: action.createdAt.toUtc(),
|
||||
lastAttemptAt: Value<DateTime?>(_utcOrNull(action.lastAttemptAt)),
|
||||
attemptCount: action.attemptCount,
|
||||
status: _pendingShareActionStatusToDb(action.status),
|
||||
);
|
||||
}
|
||||
|
||||
domain.PendingShareAction _pendingShareActionFromRow(
|
||||
db.PendingShareAction row,
|
||||
) {
|
||||
return domain.PendingShareAction(
|
||||
id: row.id,
|
||||
actionType: _pendingShareActionTypeFromDb(row.actionType),
|
||||
shareId: row.shareId,
|
||||
resourceType: row.resourceType == null
|
||||
? null
|
||||
: _shareResourceTypeFromDb(row.resourceType!),
|
||||
payloadJson: row.payloadJson,
|
||||
recipientEmailsJson: row.recipientEmailsJson,
|
||||
createdAt: _utc(row.createdAt),
|
||||
lastAttemptAt: _utcOrNull(row.lastAttemptAt),
|
||||
attemptCount: row.attemptCount,
|
||||
status: _pendingShareActionStatusFromDb(row.status),
|
||||
);
|
||||
}
|
||||
|
||||
db.ProgramsCompanion _programCompanion(domain.Program program) {
|
||||
final values = _metadataValues(program.metadata);
|
||||
return db.ProgramsCompanion(
|
||||
@ -2825,6 +3052,35 @@ domain.MediaAsset _mediaAssetFromPayload(RemoteSyncedItem item) {
|
||||
);
|
||||
}
|
||||
|
||||
domain.Program _programFromPayload(RemoteSyncedItem item) {
|
||||
final payload = item.payload;
|
||||
final metadata = _metadataFromPayload(item);
|
||||
return domain.Program(
|
||||
metadata: metadata,
|
||||
name: _stringFromPayload(payload, 'name', item.clientId),
|
||||
defaultRestSeconds: payload['defaultRestSeconds'] as int? ?? 0,
|
||||
exercises: _programExercisesFromPayload(payload['exercises'], metadata),
|
||||
);
|
||||
}
|
||||
|
||||
domain.WorkoutTemplate _workoutTemplateFromPayload(RemoteSyncedItem item) {
|
||||
final payload = item.payload;
|
||||
final metadata = _metadataFromPayload(item);
|
||||
return domain.WorkoutTemplate(
|
||||
metadata: metadata,
|
||||
name: _stringFromPayload(payload, 'name', item.clientId),
|
||||
lastStartedAt: _dateTimeFromPayload(payload['lastStartedAt']),
|
||||
programs: _workoutTemplateProgramsFromPayload(
|
||||
payload['programs'],
|
||||
metadata,
|
||||
),
|
||||
overrides: _workoutTemplateOverridesFromPayload(
|
||||
payload['overrides'],
|
||||
metadata,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
domain.EntityMetadata _metadataFromPayload(RemoteSyncedItem item) {
|
||||
final metadata = item.payload['metadata'];
|
||||
final map = metadata is Map ? Map<String, Object?>.from(metadata) : null;
|
||||
@ -2842,6 +3098,153 @@ domain.EntityMetadata _metadataFromPayload(RemoteSyncedItem item) {
|
||||
);
|
||||
}
|
||||
|
||||
List<domain.ProgramExercise> _programExercisesFromPayload(
|
||||
Object? value,
|
||||
domain.EntityMetadata parentMetadata,
|
||||
) {
|
||||
if (value is! List) {
|
||||
return const [];
|
||||
}
|
||||
return value
|
||||
.whereType<Map>()
|
||||
.map((entry) {
|
||||
final map = Map<String, Object?>.from(entry);
|
||||
final id = _stringFromPayload(map, 'id', 'program-exercise');
|
||||
return domain.ProgramExercise(
|
||||
metadata: _childMetadataFromPayload(map, id, parentMetadata),
|
||||
programId: parentMetadata.id,
|
||||
sourceExerciseId: null,
|
||||
position: map['position'] as int? ?? 0,
|
||||
exerciseNameSnapshot: _stringFromPayload(
|
||||
map,
|
||||
'exerciseNameSnapshot',
|
||||
id,
|
||||
),
|
||||
exerciseDescriptionSnapshot:
|
||||
map['exerciseDescriptionSnapshot'] as String?,
|
||||
exerciseImageMediaIdSnapshot:
|
||||
map['exerciseImageMediaIdSnapshot'] as String?,
|
||||
exerciseImageMediaIdsSnapshot: _stringListFromPayload(
|
||||
map['exerciseImageMediaIdsSnapshot'] ??
|
||||
map['imageMediaIdsSnapshot'],
|
||||
),
|
||||
exerciseVideoMediaIdSnapshot:
|
||||
map['exerciseVideoMediaIdSnapshot'] as String?,
|
||||
exerciseStepsSnapshot: _stepsFromPayload(
|
||||
map['exerciseStepsSnapshot'],
|
||||
),
|
||||
exerciseArchivedSnapshot: map['exerciseArchivedSnapshot'] == true,
|
||||
availableTimeSnapshot: map['availableTimeSnapshot'] == true,
|
||||
availableRepsSnapshot: map['availableRepsSnapshot'] == true,
|
||||
availableScoreSnapshot: map['availableScoreSnapshot'] == true,
|
||||
scoreInputModeSnapshot: _scoreInputModeFromDb(
|
||||
map['scoreInputModeSnapshot'] as String? ?? 'manual',
|
||||
),
|
||||
scoreLabelSnapshot: map['scoreLabelSnapshot'] as String?,
|
||||
scoreUnitSnapshot: map['scoreUnitSnapshot'] as String?,
|
||||
setsCount: map['setsCount'] as int? ?? 1,
|
||||
timeEnabled: map['timeEnabled'] == true,
|
||||
repsEnabled: map['repsEnabled'] == true,
|
||||
scoreEnabled: map['scoreEnabled'] == true,
|
||||
targetTimeSeconds: map['targetTimeSeconds'] as int?,
|
||||
targetReps: map['targetReps'] as int?,
|
||||
targetScore: (map['targetScore'] as num?)?.toDouble(),
|
||||
targetScoreTimeMs: map['targetScoreTimeMs'] as int?,
|
||||
restSecondsOverride: map['restSecondsOverride'] as int?,
|
||||
);
|
||||
})
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
List<domain.WorkoutTemplateProgram> _workoutTemplateProgramsFromPayload(
|
||||
Object? value,
|
||||
domain.EntityMetadata parentMetadata,
|
||||
) {
|
||||
if (value is! List) {
|
||||
return const [];
|
||||
}
|
||||
return value
|
||||
.whereType<Map>()
|
||||
.map((entry) {
|
||||
final map = Map<String, Object?>.from(entry);
|
||||
final id = _stringFromPayload(map, 'id', 'template-program');
|
||||
return domain.WorkoutTemplateProgram(
|
||||
metadata: _childMetadataFromPayload(map, id, parentMetadata),
|
||||
workoutTemplateId: parentMetadata.id,
|
||||
sourceProgramId: null,
|
||||
position: map['position'] as int? ?? 0,
|
||||
programNameSnapshot: _stringFromPayload(
|
||||
map,
|
||||
'programNameSnapshot',
|
||||
id,
|
||||
),
|
||||
defaultRestSecondsSnapshot:
|
||||
map['defaultRestSecondsSnapshot'] as int? ?? 0,
|
||||
programSnapshotJson:
|
||||
map['programSnapshotJson'] as String? ?? '{"exercises":[]}',
|
||||
);
|
||||
})
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
List<domain.WorkoutTemplateExerciseOverride>
|
||||
_workoutTemplateOverridesFromPayload(
|
||||
Object? value,
|
||||
domain.EntityMetadata parentMetadata,
|
||||
) {
|
||||
if (value is! List) {
|
||||
return const [];
|
||||
}
|
||||
return value
|
||||
.whereType<Map>()
|
||||
.map((entry) {
|
||||
final map = Map<String, Object?>.from(entry);
|
||||
final id = _stringFromPayload(map, 'id', 'template-override');
|
||||
return domain.WorkoutTemplateExerciseOverride(
|
||||
metadata: _childMetadataFromPayload(map, id, parentMetadata),
|
||||
workoutTemplateProgramId: _stringFromPayload(
|
||||
map,
|
||||
'workoutTemplateProgramId',
|
||||
'',
|
||||
),
|
||||
snapshotProgramExerciseId: _stringFromPayload(
|
||||
map,
|
||||
'snapshotProgramExerciseId',
|
||||
'',
|
||||
),
|
||||
setsCountOverride: map['setsCountOverride'] as int?,
|
||||
targetTimeSecondsOverride: map['targetTimeSecondsOverride'] as int?,
|
||||
targetRepsOverride: map['targetRepsOverride'] as int?,
|
||||
targetScoreOverride: (map['targetScoreOverride'] as num?)?.toDouble(),
|
||||
targetScoreTimeMsOverride: map['targetScoreTimeMsOverride'] as int?,
|
||||
);
|
||||
})
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
domain.EntityMetadata _childMetadataFromPayload(
|
||||
Map<String, Object?> payload,
|
||||
String id,
|
||||
domain.EntityMetadata parentMetadata,
|
||||
) {
|
||||
final metadata = payload['metadata'];
|
||||
final map = metadata is Map ? Map<String, Object?>.from(metadata) : null;
|
||||
return domain.EntityMetadata(
|
||||
id: id,
|
||||
createdAt:
|
||||
_dateTimeFromPayload(map?['createdAt']) ?? parentMetadata.createdAt,
|
||||
updatedAt:
|
||||
_dateTimeFromPayload(map?['updatedAt']) ?? parentMetadata.updatedAt,
|
||||
deletedAt: _dateTimeFromPayload(map?['deletedAt']),
|
||||
schemaVersion:
|
||||
map?['schemaVersion'] as int? ?? parentMetadata.schemaVersion,
|
||||
syncState: parentMetadata.syncState,
|
||||
localRevision: map?['localRevision'] as int? ?? 0,
|
||||
originDeviceId:
|
||||
map?['originDeviceId'] as String? ?? parentMetadata.originDeviceId,
|
||||
);
|
||||
}
|
||||
|
||||
String _stringFromPayload(
|
||||
Map<String, Object?> payload,
|
||||
String key,
|
||||
@ -2965,6 +3368,67 @@ OnlineSyncStatus _onlineSyncStatusFromDb(String value) => switch (value) {
|
||||
_ => throw domain.DomainException('Unknown sync status: $value'),
|
||||
};
|
||||
|
||||
String _shareResourceTypeToDb(domain.ShareResourceType type) => switch (type) {
|
||||
domain.ShareResourceType.program => 'program',
|
||||
domain.ShareResourceType.workoutTemplate => 'workoutTemplate',
|
||||
};
|
||||
|
||||
domain.ShareResourceType _shareResourceTypeFromDb(String value) =>
|
||||
switch (value) {
|
||||
'program' => domain.ShareResourceType.program,
|
||||
'workoutTemplate' => domain.ShareResourceType.workoutTemplate,
|
||||
_ => throw domain.DomainException('Unknown share resource type: $value'),
|
||||
};
|
||||
|
||||
String _shareInboxStatusToDb(domain.ShareInboxStatus status) =>
|
||||
switch (status) {
|
||||
domain.ShareInboxStatus.pending => 'pending',
|
||||
domain.ShareInboxStatus.accepted => 'accepted',
|
||||
domain.ShareInboxStatus.declined => 'declined',
|
||||
domain.ShareInboxStatus.revoked => 'revoked',
|
||||
};
|
||||
|
||||
domain.ShareInboxStatus _shareInboxStatusFromDb(String value) =>
|
||||
switch (value) {
|
||||
'pending' => domain.ShareInboxStatus.pending,
|
||||
'accepted' => domain.ShareInboxStatus.accepted,
|
||||
'declined' => domain.ShareInboxStatus.declined,
|
||||
'revoked' => domain.ShareInboxStatus.revoked,
|
||||
_ => throw domain.DomainException('Unknown share inbox status: $value'),
|
||||
};
|
||||
|
||||
String _pendingShareActionTypeToDb(domain.PendingShareActionType type) =>
|
||||
switch (type) {
|
||||
domain.PendingShareActionType.send => 'send',
|
||||
domain.PendingShareActionType.accept => 'accept',
|
||||
domain.PendingShareActionType.decline => 'decline',
|
||||
domain.PendingShareActionType.revoke => 'revoke',
|
||||
};
|
||||
|
||||
domain.PendingShareActionType _pendingShareActionTypeFromDb(String value) =>
|
||||
switch (value) {
|
||||
'send' => domain.PendingShareActionType.send,
|
||||
'accept' => domain.PendingShareActionType.accept,
|
||||
'decline' => domain.PendingShareActionType.decline,
|
||||
'revoke' => domain.PendingShareActionType.revoke,
|
||||
_ => throw domain.DomainException('Unknown share action type: $value'),
|
||||
};
|
||||
|
||||
String _pendingShareActionStatusToDb(domain.PendingShareActionStatus status) =>
|
||||
switch (status) {
|
||||
domain.PendingShareActionStatus.pending => 'pending',
|
||||
domain.PendingShareActionStatus.succeeded => 'succeeded',
|
||||
domain.PendingShareActionStatus.failed => 'failed',
|
||||
};
|
||||
|
||||
domain.PendingShareActionStatus _pendingShareActionStatusFromDb(String value) =>
|
||||
switch (value) {
|
||||
'pending' => domain.PendingShareActionStatus.pending,
|
||||
'succeeded' => domain.PendingShareActionStatus.succeeded,
|
||||
'failed' => domain.PendingShareActionStatus.failed,
|
||||
_ => throw domain.DomainException('Unknown share action status: $value'),
|
||||
};
|
||||
|
||||
String _activeStatusToDb(domain.ActiveWorkoutStatus status) => switch (status) {
|
||||
domain.ActiveWorkoutStatus.running => 'running',
|
||||
domain.ActiveWorkoutStatus.paused => 'paused',
|
||||
|
||||
@ -92,6 +92,57 @@ class RemoteResourceMappings extends Table {
|
||||
];
|
||||
}
|
||||
|
||||
class ShareInboxItems extends Table {
|
||||
@override
|
||||
String get tableName => 'share_inbox_items';
|
||||
|
||||
TextColumn get shareId => text()();
|
||||
TextColumn get senderUserId => text().withLength(min: 1)();
|
||||
TextColumn get resourceType => text()();
|
||||
TextColumn get payloadJson => text().withLength(min: 1)();
|
||||
TextColumn get status => text()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get updatedAt => dateTime()();
|
||||
DateTimeColumn get respondedAt => dateTime().nullable()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {shareId};
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
"CHECK (resource_type IN ('program', 'workoutTemplate'))",
|
||||
"CHECK (status IN ('pending', 'accepted', 'declined', 'revoked'))",
|
||||
];
|
||||
}
|
||||
|
||||
class PendingShareActions extends Table {
|
||||
@override
|
||||
String get tableName => 'pending_share_actions';
|
||||
|
||||
TextColumn get id => text()();
|
||||
TextColumn get actionType => text()();
|
||||
TextColumn get shareId => text().nullable()();
|
||||
TextColumn get resourceType => text().nullable()();
|
||||
TextColumn get payloadJson => text().nullable()();
|
||||
TextColumn get recipientEmailsJson => text().nullable()();
|
||||
DateTimeColumn get createdAt => dateTime()();
|
||||
DateTimeColumn get lastAttemptAt => dateTime().nullable()();
|
||||
IntColumn get attemptCount =>
|
||||
integer().customConstraint('NOT NULL CHECK (attempt_count >= 0)')();
|
||||
TextColumn get status => text()();
|
||||
|
||||
@override
|
||||
Set<Column> get primaryKey => {id};
|
||||
|
||||
@override
|
||||
List<String> get customConstraints => [
|
||||
"CHECK (action_type IN ('send', 'accept', 'decline', 'revoke'))",
|
||||
"CHECK (resource_type IS NULL OR resource_type IN "
|
||||
"('program', 'workoutTemplate'))",
|
||||
"CHECK (status IN ('pending', 'succeeded', 'failed'))",
|
||||
];
|
||||
}
|
||||
|
||||
class MediaAssets extends SyncableTable {
|
||||
@override
|
||||
String get tableName => 'media_assets';
|
||||
|
||||
Reference in New Issue
Block a user