Neue Datenbank Struktur #156
@@ -15,6 +15,43 @@
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"]
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"createdAt",
|
||||
"name"
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Bei Bei `required` dann `description` hinzufügen
|
||||
]
|
||||
}
|
||||
},
|
||||
"games": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"ruleset": {
|
||||
"type": ["string", "null"]
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Ruleset hier auch nicht null, da Game immer ein Ruleset brauch. Ruleset hier auch nicht null, da Game immer ein Ruleset brauch.
|
||||
},
|
||||
"description": {
|
||||
"type": ["string", "null"]
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Hier auch wieder description nicht Hier auch wieder description nicht `null` sondern `empty`
|
||||
},
|
||||
"color": {
|
||||
"type": ["integer", "null"]
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Warum machst du die color als integer aber die rulesets als string? Ich würde entweder beides als Warum machst du die color als integer aber die rulesets als string? Ich würde entweder beides als `String` oder beiden als `int` (bevorzugt), und beim retrieven dann als `enum` in den jeweiligen Klassen speichern.
gelbeinhalb
commented
war ausversehen 😅😅 war ausversehen 😅😅
|
||||
},
|
||||
"icon": {
|
||||
"type": ["string", "null"]
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Ich find Icons prinzipell keine schlechte Idee, aber finde sie dann eher bei z.B. Gruppen sinnvoller, weil was für Icons will man bei Games machen? Also bei Gruppen könnte man halt je nach sozialen Aspekten n icon setzen, bei Games fällt mir dazu nicht viel ein Ich find Icons prinzipell keine schlechte Idee, aber finde sie dann eher bei z.B. Gruppen sinnvoller, weil was für Icons will man bei Games machen? Also bei Gruppen könnte man halt je nach sozialen Aspekten n icon setzen, bei Games fällt mir dazu nicht viel ein
gelbeinhalb
commented
hätte sowas gedacht wie poker karten oder andere icons für die games. hätte sowas gedacht wie poker karten oder andere icons für die games.
Gruppen icons würde ich auch adden
flixcoo
commented
Das Problem ist, wenn du sagst Pokerkarten als Icon, musst du ja total viele Icons irgendwo her bekommen, die sehr speziell sind (Spielkarten, Brettspiel, etc ..), da würde ich dann vllt eher auf sowas wie emojis setzen und da ist die auswahl die thematisch dazu passt ja begrenzt. Aber sonst kann @sneeex nochmal sagen was er dazu denkt Das Problem ist, wenn du sagst Pokerkarten als Icon, musst du ja total viele Icons irgendwo her bekommen, die sehr speziell sind (Spielkarten, Brettspiel, etc ..), da würde ich dann vllt eher auf sowas wie emojis setzen und da ist die auswahl die thematisch dazu passt ja begrenzt. Aber sonst kann @sneeex nochmal sagen was er dazu denkt
sneeex
commented
weiß nicht ob ich emojis geil finde, sehe aber das problem mit den icons weiß nicht ob ich emojis geil finde, sehe aber das problem mit den icons
gelbeinhalb
commented
lass das sonst demnächst mal besprechen, was man für icons bräuchte lass das sonst demnächst mal besprechen, was man für icons bräuchte
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Meinst du hier Meinst du hier `matchId`?
gelbeinhalb
commented
bin gerade am handy, gucke später bin gerade am handy, gucke später
gelbeinhalb
commented
ja meinte ich. Ist aber jetzt komplett aus dem Team array entfernt ja meinte ich. Ist aber jetzt komplett aus dem Team array entfernt
|
||||
"anyOf": [
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Warum hier nicht Würde das konsequent machen, eins von beiden und dann das gesamte Schema durch. Warum hier nicht
```json
"type": ["string", "null"]
```
Würde das konsequent machen, eins von beiden und dann das gesamte Schema durch.
|
||||
{"type": "string"},
|
||||
{"type": "null"}
|
||||
|
gelbeinhalb marked this conversation as resolved
Outdated
flixcoo
commented
Wenn es hier um ein Wenn es hier um ein `Match` und nicht um ein `Game` geht, darf `Match`(`Game`) nicht `null` sein, weil ein Spiel immer eine Spielvorlage haben muss
|
||||
]
|
||||
},
|
||||
"groupId": {
|
||||
|
flixcoo marked this conversation as resolved
flixcoo
commented
Was soll Was soll `endedAt` für einen Sinn haben? Also wozu brauche ich den Endzeitpunkt eines Spiels? Und vor allem wie lege ich den Fest? Wenn ich denn Winner setze?
gelbeinhalb
commented
Ich dachte, dass man den Zeitpunkt später braucht, wenn das Winner Attribut entfernt wird. Später soll das ja nur calculated werden basierend auf den scores der Spieler und nicht extra gespeichert werden. Dann braucht man ja einen Weg zu sagen, ob das Spiel fertig ist oder nicht. Einen Ich dachte, dass man den Zeitpunkt später braucht, wenn das Winner Attribut entfernt wird. Später soll das ja nur calculated werden basierend auf den scores der Spieler und nicht extra gespeichert werden. Dann braucht man ja einen Weg zu sagen, ob das Spiel fertig ist oder nicht. Einen `endedAt` Timestamp fand ich besser als einen einfachen `finished` boolean
flixcoo
commented
ah okay, ja fair ah okay, ja fair
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'name': name,
|
||||
'description': description,
|
||||
'members': members.map((member) => member.toJson()).toList(),
|
||||
'memberIds': members.map((member) => member.id).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> 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<String, dynamic> 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,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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<String, dynamic> 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<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'name': name,
|
||||
'createdAt': createdAt.toIso8601String(),
|
||||
'members': members.map((member) => member.toJson()).toList(),
|
||||
'memberIds': members.map((member) => member.id).toList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<void> deleteAllData(BuildContext context) async {
|
||||
final db = Provider.of<AppDatabase>(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<String> getAppDataAsJson(BuildContext context) async {
|
||||
final db = Provider.of<AppDatabase>(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<String, dynamic> 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,
|
||||
'description': g.description,
|
||||
'createdAt': g.createdAt.toIso8601String(),
|
||||
'memberIds': (g.members).map((m) => m.id).toList(),
|
||||
}).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(),
|
||||
'gameId': m.game?.id,
|
||||
'groupId': m.group?.id,
|
||||
'playerIds': (m.players ?? []).map((p) => p.id).toList(),
|
||||
'winnerId': m.winner?.id,
|
||||
}).toList(),
|
||||
'notes': m.notes,
|
||||
})
|
||||
.toList(),
|
||||
};
|
||||
|
||||
return json.encode(jsonMap);
|
||||
@@ -107,10 +124,12 @@ class DataTransferService {
|
||||
final Map<String, dynamic> decoded = json.decode(jsonString) as Map<String, 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> teamsJson = (decoded['teams'] as List<dynamic>?) ?? [];
|
||||
final List<dynamic> matchesJson = (decoded['matches'] as List<dynamic>?) ?? [];
|
||||
|
||||
// Players
|
||||
// Import Players
|
||||
final List<Player> importedPlayers = playersJson
|
||||
.map((p) => Player.fromJson(p as Map<String, dynamic>))
|
||||
.toList();
|
||||
@@ -119,7 +138,16 @@ class DataTransferService {
|
||||
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 map = g as Map<String, dynamic>;
|
||||
final memberIds = (map['memberIds'] as List<dynamic>? ?? []).cast<String>();
|
||||
@@ -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<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 map = m as Map<String, dynamic>;
|
||||
|
||||
final String? gameId = map['gameId'] as String?;
|
||||
final String? groupId = map['groupId'] as 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 players = playerIds
|
||||
.map((id) => playerById[id])
|
||||
.whereType<Player>()
|
||||
.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;
|
||||
|
||||
Ich glaube es würde mehr Sinn machen, die
descriptionnicht nullable zu machen, sondern einfach durch leeren String zu ersetzen, weil wir uns dann viel null checken sparen