diff --git a/assets/schema.json b/assets/schema.json index b3a8a2c..3aab588 100644 --- a/assets/schema.json +++ b/assets/schema.json @@ -15,6 +15,43 @@ }, "name": { "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": [ @@ -25,6 +62,38 @@ } }, "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", "items": { "type": "object", @@ -67,6 +136,12 @@ "createdAt": { "type": "string" }, + "gameId": { + "anyOf": [ + {"type": "string"}, + {"type": "null"} + ] + }, "groupId": { "anyOf": [ {"type": "string"}, @@ -79,7 +154,7 @@ "type": "string" } }, - "winnerId": { + "notes": { "anyOf": [ {"type": "string"}, {"type": "null"} @@ -90,7 +165,6 @@ "id", "name", "createdAt", - "groupId", "playerIds" ] } @@ -98,7 +172,9 @@ }, "required": [ "players", + "games", "groups", + "teams", "matches" ] } \ No newline at end of file diff --git a/lib/data/dto/group.dart b/lib/data/dto/group.dart index 4a6f7cd..a69c982 100644 --- a/lib/data/dto/group.dart +++ b/lib/data/dto/group.dart @@ -23,22 +23,21 @@ class Group { 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 json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), name = json['name'], description = json['description'], - members = (json['members'] as List) - .map((memberJson) => Player.fromJson(memberJson)) - .toList(); + members = []; // Populated during import via DataTransferService - /// Converts the Group instance to a JSON object. + /// Converts the Group instance to a JSON object using normalized format (memberIds only). Map toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), 'name': name, 'description': description, - 'members': members.map((member) => member.toJson()).toList(), + 'memberIds': members.map((member) => member.id).toList(), }; } diff --git a/lib/data/dto/match.dart b/lib/data/dto/match.dart index 6963b4f..b71757a 100644 --- a/lib/data/dto/match.dart +++ b/lib/data/dto/match.dart @@ -28,31 +28,28 @@ class Match { @override 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 json) : id = json['id'], createdAt = DateTime.parse(json['createdAt']), name = json['name'], - game = json['game'] != null ? Game.fromJson(json['game']) : null, - group = json['group'] != null ? Group.fromJson(json['group']) : null, - players = json['players'] != null - ? (json['players'] as List) - .map((playerJson) => Player.fromJson(playerJson)) - .toList() - : null, + game = null, // Populated during import via DataTransferService + group = null, // Populated during import via DataTransferService + players = [], // Populated during import via DataTransferService 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 toJson() => { 'id': id, 'createdAt': createdAt.toIso8601String(), 'name': name, - 'game': game?.toJson(), - 'group': group?.toJson(), - 'players': players?.map((player) => player.toJson()).toList(), + 'gameId': game?.id, + 'groupId': group?.id, + 'playerIds': (players ?? []).map((player) => player.id).toList(), 'notes': notes, }; } diff --git a/lib/data/dto/team.dart b/lib/data/dto/team.dart index a3f78d5..46eb5ca 100644 --- a/lib/data/dto/team.dart +++ b/lib/data/dto/team.dart @@ -21,21 +21,20 @@ class Team { 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 json) : id = json['id'], name = json['name'], createdAt = DateTime.parse(json['createdAt']), - members = (json['members'] as List) - .map((memberJson) => Player.fromJson(memberJson)) - .toList(); + members = []; // Populated during import via DataTransferService - /// Converts the Team instance to a JSON object. + /// Converts the Team instance to a JSON object using normalized format (memberIds only). Map toJson() => { 'id': id, 'name': name, 'createdAt': createdAt.toIso8601String(), - 'members': members.map((member) => member.toJson()).toList(), + 'memberIds': members.map((member) => member.id).toList(), }; } diff --git a/lib/services/data_transfer_service.dart b/lib/services/data_transfer_service.dart index 8767c59..801fd96 100644 --- a/lib/services/data_transfer_service.dart +++ b/lib/services/data_transfer_service.dart @@ -6,9 +6,11 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:game_tracker/core/enums.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/match.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:provider/provider.dart'; @@ -17,39 +19,54 @@ class DataTransferService { static Future deleteAllData(BuildContext context) async { final db = Provider.of(context, listen: false); await db.matchDao.deleteAllMatches(); + await db.teamDao.deleteAllTeams(); await db.groupDao.deleteAllGroups(); + await db.gameDao.deleteAllGames(); await db.playerDao.deleteAllPlayers(); } /// 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 getAppDataAsJson(BuildContext context) async { final db = Provider.of(context, listen: false); final matches = await db.matchDao.getAllMatches(); final groups = await db.groupDao.getAllGroups(); 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 jsonMap = { 'players': players.map((p) => p.toJson()).toList(), - + 'games': games.map((g) => g.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(), - + 'id': g.id, + 'name': g.name, + 'description': g.description, + 'createdAt': g.createdAt.toIso8601String(), + '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 .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(), + 'id': m.id, + 'name': m.name, + 'createdAt': m.createdAt.toIso8601String(), + 'gameId': m.game?.id, + 'groupId': m.group?.id, + 'playerIds': (m.players ?? []).map((p) => p.id).toList(), + 'notes': m.notes, + }) + .toList(), }; return json.encode(jsonMap); @@ -107,10 +124,12 @@ class DataTransferService { final Map decoded = json.decode(jsonString) as Map; final List playersJson = (decoded['players'] as List?) ?? []; + final List gamesJson = (decoded['games'] as List?) ?? []; final List groupsJson = (decoded['groups'] as List?) ?? []; + final List teamsJson = (decoded['teams'] as List?) ?? []; final List matchesJson = (decoded['matches'] as List?) ?? []; - // Players + // Import Players final List importedPlayers = playersJson .map((p) => Player.fromJson(p as Map)) .toList(); @@ -119,7 +138,16 @@ class DataTransferService { for (final p in importedPlayers) p.id: p, }; - // Groups + // Import Games + final List importedGames = gamesJson + .map((g) => Game.fromJson(g as Map)) + .toList(); + + final Map gameById = { + for (final g in importedGames) g.id: g, + }; + + // Import Groups final List importedGroups = groupsJson.map((g) { final map = g as Map; final memberIds = (map['memberIds'] as List? ?? []).cast(); @@ -132,6 +160,7 @@ class DataTransferService { return Group( id: map['id'] as String, name: map['name'] as String, + description: map['description'] as String?, members: members, createdAt: DateTime.parse(map['createdAt'] as String), ); @@ -141,33 +170,59 @@ class DataTransferService { for (final g in importedGroups) g.id: g, }; - // Matches + // Import Teams + final List importedTeams = teamsJson.map((t) { + final map = t as Map; + final memberIds = (map['memberIds'] as List? ?? []).cast(); + + final members = memberIds + .map((id) => playerById[id]) + .whereType() + .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 teamById = { + for (final t in importedTeams) t.id: t, + }; + + // Import Matches final List importedMatches = matchesJson.map((m) { final map = m as Map; + final String? gameId = map['gameId'] as String?; final String? groupId = map['groupId'] as String?; final List playerIds = (map['playerIds'] as List? ?? []).cast(); - final String? winnerId = map['winnerId'] as String?; + final game = (gameId == null) ? null : gameById[gameId]; 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, + game: game, group: group, - players: players, + players: players.isNotEmpty ? players : null, createdAt: DateTime.parse(map['createdAt'] as String), - winner: winner, + notes: map['notes'] as String?, ); }).toList(); + // Import all data into the database await db.playerDao.addPlayersAsList(players: importedPlayers); + await db.gameDao.addGamesAsList(games: importedGames); await db.groupDao.addGroupsAsList(groups: importedGroups); + await db.teamDao.addTeamsAsList(teams: importedTeams); await db.matchDao.addMatchAsList(matches: importedMatches); return ImportResult.success;