fix data import and export
Some checks failed
Pull Request Pipeline / test (pull_request) Successful in 2m8s
Pull Request Pipeline / lint (pull_request) Failing after 2m18s

This commit is contained in:
gelbeinhalb
2026-01-21 15:43:56 +01:00
parent bd5e38a3ca
commit 7339194ba0
5 changed files with 176 additions and 50 deletions

View File

@@ -15,6 +15,43 @@
}, },
"name": { "name": {
"type": "string" "type": "string"
},
"description": {
"type": ["string", "null"]
}
},
"required": [
"id",
"createdAt",
"name"
]
}
},
"games": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"name": {
"type": "string"
},
"ruleset": {
"type": ["string", "null"]
},
"description": {
"type": ["string", "null"]
},
"color": {
"type": ["integer", "null"]
},
"icon": {
"type": ["string", "null"]
} }
}, },
"required": [ "required": [
@@ -25,6 +62,38 @@
} }
}, },
"groups": { "groups": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": ["string", "null"]
},
"memberIds": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"id",
"name",
"createdAt",
"memberIds"
]
}
},
"teams": {
"type": "array", "type": "array",
"items": { "items": {
"type": "object", "type": "object",
@@ -67,6 +136,12 @@
"createdAt": { "createdAt": {
"type": "string" "type": "string"
}, },
"gameId": {
"anyOf": [
{"type": "string"},
{"type": "null"}
]
},
"groupId": { "groupId": {
"anyOf": [ "anyOf": [
{"type": "string"}, {"type": "string"},
@@ -79,7 +154,7 @@
"type": "string" "type": "string"
} }
}, },
"winnerId": { "notes": {
"anyOf": [ "anyOf": [
{"type": "string"}, {"type": "string"},
{"type": "null"} {"type": "null"}
@@ -90,7 +165,6 @@
"id", "id",
"name", "name",
"createdAt", "createdAt",
"groupId",
"playerIds" "playerIds"
] ]
} }
@@ -98,7 +172,9 @@
}, },
"required": [ "required": [
"players", "players",
"games",
"groups", "groups",
"teams",
"matches" "matches"
] ]
} }

View File

@@ -23,22 +23,21 @@ class Group {
return 'Group{id: $id, name: $name, description: $description, members: $members}'; return 'Group{id: $id, name: $name, description: $description, members: $members}';
} }
/// Creates a Group instance from a JSON object. /// Creates a Group instance from a JSON object (memberIds format).
/// Player objects are reconstructed from memberIds by the DataTransferService.
Group.fromJson(Map<String, dynamic> json) Group.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
name = json['name'], name = json['name'],
description = json['description'], description = json['description'],
members = (json['members'] as List) members = []; // Populated during import via DataTransferService
.map((memberJson) => Player.fromJson(memberJson))
.toList();
/// Converts the Group instance to a JSON object. /// Converts the Group instance to a JSON object using normalized format (memberIds only).
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),
'name': name, 'name': name,
'description': description, 'description': description,
'members': members.map((member) => member.toJson()).toList(), 'memberIds': members.map((member) => member.id).toList(),
}; };
} }

View File

@@ -28,31 +28,28 @@ class Match {
@override @override
String toString() { String toString() {
return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes, winner: $winner}'; return 'Match{id: $id, name: $name, game: $game, group: $group, players: $players, notes: $notes}';
} }
/// Creates a Match instance from a JSON object. /// Creates a Match instance from a JSON object (ID references format).
/// Related objects are reconstructed from IDs by the DataTransferService.
Match.fromJson(Map<String, dynamic> json) Match.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
name = json['name'], name = json['name'],
game = json['game'] != null ? Game.fromJson(json['game']) : null, game = null, // Populated during import via DataTransferService
group = json['group'] != null ? Group.fromJson(json['group']) : null, group = null, // Populated during import via DataTransferService
players = json['players'] != null players = [], // Populated during import via DataTransferService
? (json['players'] as List)
.map((playerJson) => Player.fromJson(playerJson))
.toList()
: null,
notes = json['notes']; notes = json['notes'];
/// Converts the Match instance to a JSON object. /// Converts the Match instance to a JSON object using normalized format (ID references only).
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),
'name': name, 'name': name,
'game': game?.toJson(), 'gameId': game?.id,
'group': group?.toJson(), 'groupId': group?.id,
'players': players?.map((player) => player.toJson()).toList(), 'playerIds': (players ?? []).map((player) => player.id).toList(),
'notes': notes, 'notes': notes,
}; };
} }

View File

@@ -21,21 +21,20 @@ class Team {
return 'Team{id: $id, name: $name, members: $members}'; return 'Team{id: $id, name: $name, members: $members}';
} }
/// Creates a Team instance from a JSON object. /// Creates a Team instance from a JSON object (memberIds format).
/// Player objects are reconstructed from memberIds by the DataTransferService.
Team.fromJson(Map<String, dynamic> json) Team.fromJson(Map<String, dynamic> json)
: id = json['id'], : id = json['id'],
name = json['name'], name = json['name'],
createdAt = DateTime.parse(json['createdAt']), createdAt = DateTime.parse(json['createdAt']),
members = (json['members'] as List) members = []; // Populated during import via DataTransferService
.map((memberJson) => Player.fromJson(memberJson))
.toList();
/// Converts the Team instance to a JSON object. /// Converts the Team instance to a JSON object using normalized format (memberIds only).
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
'name': name, 'name': name,
'createdAt': createdAt.toIso8601String(), 'createdAt': createdAt.toIso8601String(),
'members': members.map((member) => member.toJson()).toList(), 'memberIds': members.map((member) => member.id).toList(),
}; };
} }

View File

@@ -6,9 +6,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:game_tracker/core/enums.dart'; import 'package:game_tracker/core/enums.dart';
import 'package:game_tracker/data/db/database.dart'; import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/dto/game.dart';
import 'package:game_tracker/data/dto/group.dart'; import 'package:game_tracker/data/dto/group.dart';
import 'package:game_tracker/data/dto/match.dart'; import 'package:game_tracker/data/dto/match.dart';
import 'package:game_tracker/data/dto/player.dart'; import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/data/dto/team.dart';
import 'package:json_schema/json_schema.dart'; import 'package:json_schema/json_schema.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -17,39 +19,54 @@ class DataTransferService {
static Future<void> deleteAllData(BuildContext context) async { static Future<void> deleteAllData(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false); final db = Provider.of<AppDatabase>(context, listen: false);
await db.matchDao.deleteAllMatches(); await db.matchDao.deleteAllMatches();
await db.teamDao.deleteAllTeams();
await db.groupDao.deleteAllGroups(); await db.groupDao.deleteAllGroups();
await db.gameDao.deleteAllGames();
await db.playerDao.deleteAllPlayers(); await db.playerDao.deleteAllPlayers();
} }
/// Retrieves all application data and converts it to a JSON string. /// Retrieves all application data and converts it to a JSON string.
/// Returns the JSON string representation of the data. /// Returns the JSON string representation of the data in normalized format.
static Future<String> getAppDataAsJson(BuildContext context) async { static Future<String> getAppDataAsJson(BuildContext context) async {
final db = Provider.of<AppDatabase>(context, listen: false); final db = Provider.of<AppDatabase>(context, listen: false);
final matches = await db.matchDao.getAllMatches(); final matches = await db.matchDao.getAllMatches();
final groups = await db.groupDao.getAllGroups(); final groups = await db.groupDao.getAllGroups();
final players = await db.playerDao.getAllPlayers(); final players = await db.playerDao.getAllPlayers();
final games = await db.gameDao.getAllGames();
final teams = await db.teamDao.getAllTeams();
// Construct a JSON representation of the data // Construct a JSON representation of the data in normalized format
final Map<String, dynamic> jsonMap = { final Map<String, dynamic> jsonMap = {
'players': players.map((p) => p.toJson()).toList(), 'players': players.map((p) => p.toJson()).toList(),
'games': games.map((g) => g.toJson()).toList(),
'groups': groups 'groups': groups
.map((g) => { .map((g) => {
'id': g.id, 'id': g.id,
'name': g.name, 'name': g.name,
'createdAt': g.createdAt.toIso8601String(), 'description': g.description,
'memberIds': (g.members).map((m) => m.id).toList(), 'createdAt': g.createdAt.toIso8601String(),
}).toList(), 'memberIds': (g.members).map((m) => m.id).toList(),
})
.toList(),
'teams': teams
.map((t) => {
'id': t.id,
'name': t.name,
'createdAt': t.createdAt.toIso8601String(),
'memberIds': (t.members).map((m) => m.id).toList(),
})
.toList(),
'matches': matches 'matches': matches
.map((m) => { .map((m) => {
'id': m.id, 'id': m.id,
'name': m.name, 'name': m.name,
'createdAt': m.createdAt.toIso8601String(), 'createdAt': m.createdAt.toIso8601String(),
'groupId': m.group?.id, 'gameId': m.game?.id,
'playerIds': (m.players ?? []).map((p) => p.id).toList(), 'groupId': m.group?.id,
'winnerId': m.winner?.id, 'playerIds': (m.players ?? []).map((p) => p.id).toList(),
}).toList(), 'notes': m.notes,
})
.toList(),
}; };
return json.encode(jsonMap); return json.encode(jsonMap);
@@ -107,10 +124,12 @@ class DataTransferService {
final Map<String, dynamic> decoded = json.decode(jsonString) as Map<String, dynamic>; final Map<String, dynamic> decoded = json.decode(jsonString) as Map<String, dynamic>;
final List<dynamic> playersJson = (decoded['players'] as List<dynamic>?) ?? []; final List<dynamic> playersJson = (decoded['players'] as List<dynamic>?) ?? [];
final List<dynamic> gamesJson = (decoded['games'] as List<dynamic>?) ?? [];
final List<dynamic> groupsJson = (decoded['groups'] as List<dynamic>?) ?? []; final List<dynamic> groupsJson = (decoded['groups'] as List<dynamic>?) ?? [];
final List<dynamic> teamsJson = (decoded['teams'] as List<dynamic>?) ?? [];
final List<dynamic> matchesJson = (decoded['matches'] as List<dynamic>?) ?? []; final List<dynamic> matchesJson = (decoded['matches'] as List<dynamic>?) ?? [];
// Players // Import Players
final List<Player> importedPlayers = playersJson final List<Player> importedPlayers = playersJson
.map((p) => Player.fromJson(p as Map<String, dynamic>)) .map((p) => Player.fromJson(p as Map<String, dynamic>))
.toList(); .toList();
@@ -119,7 +138,16 @@ class DataTransferService {
for (final p in importedPlayers) p.id: p, for (final p in importedPlayers) p.id: p,
}; };
// Groups // Import Games
final List<Game> importedGames = gamesJson
.map((g) => Game.fromJson(g as Map<String, dynamic>))
.toList();
final Map<String, Game> gameById = {
for (final g in importedGames) g.id: g,
};
// Import Groups
final List<Group> importedGroups = groupsJson.map((g) { final List<Group> importedGroups = groupsJson.map((g) {
final map = g as Map<String, dynamic>; final map = g as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>(); final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>();
@@ -132,6 +160,7 @@ class DataTransferService {
return Group( return Group(
id: map['id'] as String, id: map['id'] as String,
name: map['name'] as String, name: map['name'] as String,
description: map['description'] as String?,
members: members, members: members,
createdAt: DateTime.parse(map['createdAt'] as String), createdAt: DateTime.parse(map['createdAt'] as String),
); );
@@ -141,33 +170,59 @@ class DataTransferService {
for (final g in importedGroups) g.id: g, for (final g in importedGroups) g.id: g,
}; };
// Matches // Import Teams
final List<Team> importedTeams = teamsJson.map((t) {
final map = t as Map<String, dynamic>;
final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>();
final members = memberIds
.map((id) => playerById[id])
.whereType<Player>()
.toList();
return Team(
id: map['id'] as String,
name: map['name'] as String,
members: members,
createdAt: DateTime.parse(map['createdAt'] as String),
);
}).toList();
final Map<String, Team> teamById = {
for (final t in importedTeams) t.id: t,
};
// Import Matches
final List<Match> importedMatches = matchesJson.map((m) { final List<Match> importedMatches = matchesJson.map((m) {
final map = m as Map<String, dynamic>; final map = m as Map<String, dynamic>;
final String? gameId = map['gameId'] as String?;
final String? groupId = map['groupId'] as String?; final String? groupId = map['groupId'] as String?;
final List<String> playerIds = (map['playerIds'] as List<dynamic>? ?? []).cast<String>(); final List<String> playerIds = (map['playerIds'] as List<dynamic>? ?? []).cast<String>();
final String? winnerId = map['winnerId'] as String?;
final game = (gameId == null) ? null : gameById[gameId];
final group = (groupId == null) ? null : groupById[groupId]; final group = (groupId == null) ? null : groupById[groupId];
final players = playerIds final players = playerIds
.map((id) => playerById[id]) .map((id) => playerById[id])
.whereType<Player>() .whereType<Player>()
.toList(); .toList();
final winner = (winnerId == null) ? null : playerById[winnerId];
return Match( return Match(
id: map['id'] as String, id: map['id'] as String,
name: map['name'] as String, name: map['name'] as String,
game: game,
group: group, group: group,
players: players, players: players.isNotEmpty ? players : null,
createdAt: DateTime.parse(map['createdAt'] as String), createdAt: DateTime.parse(map['createdAt'] as String),
winner: winner, notes: map['notes'] as String?,
); );
}).toList(); }).toList();
// Import all data into the database
await db.playerDao.addPlayersAsList(players: importedPlayers); await db.playerDao.addPlayersAsList(players: importedPlayers);
await db.gameDao.addGamesAsList(games: importedGames);
await db.groupDao.addGroupsAsList(groups: importedGroups); await db.groupDao.addGroupsAsList(groups: importedGroups);
await db.teamDao.addTeamsAsList(teams: importedTeams);
await db.matchDao.addMatchAsList(matches: importedMatches); await db.matchDao.addMatchAsList(matches: importedMatches);
return ImportResult.success; return ImportResult.success;