From 4a67dae45617178f7103394434b7bc83d9415dee Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Fri, 2 Jan 2026 20:37:10 +0100 Subject: [PATCH 1/6] change import/export json logic to remove redundant data --- lib/services/data_transfer_service.dart | 128 +++++++++++++++++------- 1 file changed, 91 insertions(+), 37 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 957d8fd..2fc5c42 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -31,9 +31,25 @@ class DataTransferService { // Construct a JSON representation of the data final Map jsonMap = { - 'matches': matches.map((match) => match.toJson()).toList(), - 'groups': groups.map((group) => group.toJson()).toList(), - 'players': players.map((player) => player.toJson()).toList(), + 'players': players.map((p) => p.toJson()).toList(), + + 'groups': groups + .map((g) => { + 'id': g.id, + 'name': g.name, + 'createdAt': g.createdAt.toIso8601String(), + 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + }).toList(), + + 'matches': matches + .map((m) => { + 'id': m.id, + 'name': m.name, + 'createdAt': m.createdAt.toIso8601String(), + 'groupId': m.group?.id, + 'playerIds': (m.players ?? []).map((p) => p.id).toList(), + 'winnerId': m.winner?.id, + }).toList(), }; return json.encode(jsonMap); @@ -46,7 +62,7 @@ class DataTransferService { /// [fileName] The desired name for the exported file (without extension). static Future exportData( String jsonString, - String fileName, + String fileName ) async { try { final bytes = Uint8List.fromList(utf8.encode(jsonString)); @@ -54,11 +70,13 @@ class DataTransferService { fileName: '$fileName.json', bytes: bytes, ); + if (path == null) { return ExportResult.canceled; } else { return ExportResult.success; } + } catch (e, stack) { print('[exportData] $e'); print(stack); @@ -81,42 +99,78 @@ class DataTransferService { try { final jsonString = await _readFileContent(path.files.single); - if (jsonString == null) { - return ImportResult.fileReadError; - } + if (jsonString == null) return ImportResult.fileReadError; - if (await _validateJsonSchema(jsonString)) { - final Map jsonData = - json.decode(jsonString) as Map; + final isValid = await _validateJsonSchema(jsonString); + if (!isValid) return ImportResult.invalidSchema; - final List? matchesJson = - jsonData['matches'] as List?; - final List? groupsJson = jsonData['groups'] as List?; - final List? playersJson = - jsonData['players'] as List?; + final dynamic decoded = json.decode(jsonString); + if (decoded is! Map) return ImportResult.invalidSchema; - final List importedMatches = - matchesJson - ?.map((g) => Match.fromJson(g as Map)) - .toList() ?? - []; - final List importedGroups = - groupsJson - ?.map((g) => Group.fromJson(g as Map)) - .toList() ?? - []; - final List importedPlayers = - playersJson - ?.map((p) => Player.fromJson(p as Map)) - .toList() ?? - []; + final List playersJson = (decoded['players'] as List?) ?? []; + final List groupsJson = (decoded['groups'] as List?) ?? []; + final List matchesJson = (decoded['matches'] as List?) ?? []; + + // Players + final List importedPlayers = playersJson + .map((p) => Player.fromJson(p as Map)) + .toList(); + + final Map playerById = { + for (final p in importedPlayers) p.id: p, + }; + + // Groups + final List importedGroups = groupsJson.map((g) { + final map = g as Map; + final memberIds = (map['memberIds'] as List? ?? []).cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .toList(); + + return Group( + id: map['id'] as String, + name: map['name'] as String, + members: members, + createdAt: DateTime.parse(map['createdAt'] as String), + ); + }).toList(); + + final Map groupById = { + for (final g in importedGroups) g.id: g, + }; + + // Matches + final List importedMatches = matchesJson.map((m) { + final map = m as Map; + + final String? groupId = map['groupId'] as String?; + final List playerIds = (map['playerIds'] as List? ?? []).cast(); + final String? winnerId = map['winnerId'] as String?; + + final group = (groupId == null) ? null : groupById[groupId]; + final players = playerIds + .map((id) => playerById[id]) + .whereType() + .toList(); + final winner = (winnerId == null) ? null : playerById[winnerId]; + + return Match( + id: map['id'] as String, + name: map['name'] as String, + group: group, + players: players, + createdAt: DateTime.parse(map['createdAt'] as String), + winner: winner, + ); + }).toList(); + + await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.matchDao.addMatchAsList(matches: importedMatches); - await db.playerDao.addPlayersAsList(players: importedPlayers); - await db.groupDao.addGroupsAsList(groups: importedGroups); - await db.matchDao.addMatchAsList(matches: importedMatches); - } else { - return ImportResult.invalidSchema; - } return ImportResult.success; } on FormatException catch (e, stack) { print('[importData] FormatException'); @@ -159,4 +213,4 @@ class DataTransferService { return false; } } -} +} \ No newline at end of file -- 2.49.1 From cecfd5da4e817d78f9c1ff7282fb97832af786d5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:19:58 +0100 Subject: [PATCH 2/6] update schema.json to current data import version --- assets/schema.json | 244 +++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 162 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index c80915c..fa748d9 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -2,178 +2,98 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "properties": { - "games": { + "players": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "players": { - "type": [ - "array", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "group": { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } - }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] + "createdAt": { + "type": "string" }, - "winner": { - "type": ["object","null"] - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "name": { + "type": "string" + } + }, + "required": [ + "id", + "createdAt", + "name" + ] + } }, "groups": { "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - }, - "members": { - "type": "array", - "items": [ - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { - "type": "string" - } - }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] - } + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" }, - "required": [ - "id", - "createdAt", - "name", - "members" - ] - } - ] + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "memberIds": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "id", + "name", + "createdAt", + "memberIds" + ] + } }, - "players": { + "matches": { "type": "array", - "items": [ - { - "type": [ - "object", - "null" - ], - "properties": { - "id": { - "type": "string" - }, - "createdAt": { - "type": "string" - }, - "name": { + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "playerIds": { + "type": "array", + "items": { "type": "string" } }, - "required": [ - "id", - "createdAt", - "name" - ] - } - ] + "winnerId": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "createdAt", + "groupId", + "playerIds", + "winnerId" + ] + } } - } -} - + }, + "required": [ + "players", + "groups", + "matches" + ] +} \ No newline at end of file -- 2.49.1 From 4c084cae4aa12f795cf4cd7e787e7df7585f2232 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Sat, 3 Jan 2026 13:20:36 +0100 Subject: [PATCH 3/6] remove ?? as members cant be null --- lib/services/data_transfer_service.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 2fc5c42..e60240c 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -38,7 +38,7 @@ class DataTransferService { 'id': g.id, 'name': g.name, 'createdAt': g.createdAt.toIso8601String(), - 'memberIds': (g.members ?? []).map((m) => m.id).toList(), + 'memberIds': (g.members).map((m) => m.id).toList(), }).toList(), 'matches': matches -- 2.49.1 From 7f6c1cb9a65c85d7bd087ef2df3fff9eb0152476 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:40:57 +0100 Subject: [PATCH 4/6] change InsertMode to insertOrIgnore not insertOrReplace --- lib/data/dao/group_dao.dart | 8 ++++++-- lib/data/dao/match_dao.dart | 8 ++++++-- lib/data/dao/player_dao.dart | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/data/dao/group_dao.dart b/lib/data/dao/group_dao.dart index 9802203..98c602a 100644 --- a/lib/data/dao/group_dao.dart +++ b/lib/data/dao/group_dao.dart @@ -95,6 +95,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } // Insert unique groups in batch + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when groups already exist await db.batch( (b) => b.insertAll( groupTable, @@ -107,7 +109,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -120,6 +122,8 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -132,7 +136,7 @@ class GroupDao extends DatabaseAccessor with _$GroupDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/match_dao.dart b/lib/data/dao/match_dao.dart index b56a38b..160686a 100644 --- a/lib/data/dao/match_dao.dart +++ b/lib/data/dao/match_dao.dart @@ -124,6 +124,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ); // Add all groups of the matches in batch + // Using insertOrIgnore to avoid overwriting existing groups (which would + // trigger cascade deletes on player_group associations) await db.batch( (b) => b.insertAll( db.groupTable, @@ -137,7 +139,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); @@ -158,6 +160,8 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { } if (uniquePlayers.isNotEmpty) { + // Using insertOrIgnore to avoid triggering cascade deletes on + // player_group/player_match associations when players already exist await db.batch( (b) => b.insertAll( db.playerTable, @@ -170,7 +174,7 @@ class MatchDao extends DatabaseAccessor with _$MatchDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); } diff --git a/lib/data/dao/player_dao.dart b/lib/data/dao/player_dao.dart index 8a58504..c8db800 100644 --- a/lib/data/dao/player_dao.dart +++ b/lib/data/dao/player_dao.dart @@ -50,6 +50,8 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { } /// Adds multiple [players] to the database in a batch operation. + /// Uses insertOrIgnore to avoid triggering cascade deletes on + /// player_group associations when players already exist. Future addPlayersAsList({required List players}) async { if (players.isEmpty) return false; @@ -65,7 +67,7 @@ class PlayerDao extends DatabaseAccessor with _$PlayerDaoMixin { ), ) .toList(), - mode: InsertMode.insertOrReplace, + mode: InsertMode.insertOrIgnore, ), ); -- 2.49.1 From 7b2314a25e9543fe202e918ae6181d0d4509aecb Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Mon, 5 Jan 2026 11:46:26 +0100 Subject: [PATCH 5/6] fix null error for winnerId and groupId in match import --- assets/schema.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/assets/schema.json b/assets/schema.json index fa748d9..b3a8a2c 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -68,7 +68,10 @@ "type": "string" }, "groupId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] }, "playerIds": { "type": "array", @@ -77,7 +80,10 @@ } }, "winnerId": { - "type": "string" + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] } }, "required": [ @@ -85,8 +91,7 @@ "name", "createdAt", "groupId", - "playerIds", - "winnerId" + "playerIds" ] } } -- 2.49.1 From 99044c0ea0219deefe0f703d95f86ebf9a785ed5 Mon Sep 17 00:00:00 2001 From: gelbeinhalb Date: Wed, 7 Jan 2026 12:00:25 +0100 Subject: [PATCH 6/6] remove unnecessary check --- lib/services/data_transfer_service.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index e60240c..8767c59 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -104,8 +104,7 @@ class DataTransferService { final isValid = await _validateJsonSchema(jsonString); if (!isValid) return ImportResult.invalidSchema; - final dynamic decoded = json.decode(jsonString); - if (decoded is! Map) return ImportResult.invalidSchema; + final Map decoded = json.decode(jsonString) as Map; final List playersJson = (decoded['players'] as List?) ?? []; final List groupsJson = (decoded['groups'] as List?) ?? []; -- 2.49.1