16 Commits

Author SHA1 Message Date
13b3d2cad9 Merge branch 'development' into bug/195-datenbank-onDelete-ueberpruefen
All checks were successful
Pull Request Pipeline / lint (pull_request) Successful in 45s
Pull Request Pipeline / test (pull_request) Successful in 47s
Pull Request Pipeline / localizations (pull_request) Successful in 25s
2026-05-23 16:15:00 +02:00
4c77eaa8c9 Merge branch 'development' into bug/195-datenbank-onDelete-ueberpruefen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 45s
Pull Request Pipeline / lint (pull_request) Successful in 53s
2026-05-21 19:55:41 +00:00
gelbeinhalb
e6e065ed44 deleted is not required anymore
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 47s
Pull Request Pipeline / lint (pull_request) Successful in 55s
2026-05-12 21:43:36 +02:00
gelbeinhalb
c674c8e248 regenerate database.g.dart 2026-05-12 21:43:09 +02:00
gelbeinhalb
a6e0971208 deleted teams get ignored when includeDeleted is false
Some checks failed
Pull Request Pipeline / test (pull_request) Failing after 46s
Pull Request Pipeline / lint (pull_request) Successful in 54s
2026-05-12 20:45:12 +02:00
gelbeinhalb
b1934e64bd deleted players get ignored when includeDeleted is false 2026-05-12 20:43:28 +02:00
gelbeinhalb
0870f418d5 deleted matches get ignored when includeDeleted is false
Some checks failed
Pull Request Pipeline / test (pull_request) Failing after 46s
Pull Request Pipeline / lint (pull_request) Successful in 54s
2026-05-12 20:35:09 +02:00
gelbeinhalb
8f4254748b deleted groups get ignored when includeDeleted is false 2026-05-12 20:33:02 +02:00
gelbeinhalb
36ad2ab446 deleted games get ignored when includeDeleted is false 2026-05-12 20:30:46 +02:00
gelbeinhalb
b3b3def07a add deleted to schema 2026-05-12 20:23:41 +02:00
gelbeinhalb
d3c9827529 remove useless deleted attributes 2026-05-12 20:20:40 +02:00
gelbeinhalb
81b73beeef Merge remote-tracking branch 'origin/development' into bug/195-datenbank-onDelete-ueberpruefen
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 46s
Pull Request Pipeline / lint (pull_request) Successful in 53s
# Conflicts:
#	assets/schema.json
#	lib/data/db/tables/player_match_table.dart
#	lib/data/models/game.dart
2026-05-12 20:19:50 +02:00
gelbeinhalb
421fe1a2bf add deleted to schema
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 43s
Pull Request Pipeline / lint (pull_request) Successful in 50s
2026-05-05 10:35:04 +02:00
gelbeinhalb
66e657235a add deleted attribute
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 45s
Pull Request Pipeline / lint (pull_request) Successful in 46s
2026-04-30 11:56:15 +02:00
gelbeinhalb
1ec1df3514 Merge remote-tracking branch 'origin/development' into bug/195-datenbank-onDelete-ueberpruefen
# Conflicts:
#	lib/data/db/tables/player_table.dart
2026-04-30 11:20:47 +02:00
gelbeinhalb
8bd53a69c3 add soft delete parameter
All checks were successful
Pull Request Pipeline / test (pull_request) Successful in 44s
Pull Request Pipeline / lint (pull_request) Successful in 47s
2026-04-19 12:39:14 +02:00
17 changed files with 616 additions and 83 deletions

View File

@@ -18,6 +18,9 @@
},
"description": {
"type": "string"
},
"deleted": {
"type": "boolean"
}
},
"additionalProperties": false,
@@ -54,6 +57,9 @@
},
"icon": {
"type": "string"
},
"deleted": {
"type": "boolean"
}
},
"additionalProperties": false,
@@ -90,6 +96,9 @@
"items": {
"type": "string"
}
},
"deleted": {
"type": "boolean"
}
},
"additionalProperties": false,
@@ -168,6 +177,9 @@
},
"teams": {
"type": ["array", "null"]
},
"deleted": {
"type": "boolean"
}
},
"additionalProperties": false,

View File

@@ -65,25 +65,40 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
/* Read */
/// Retrieves the total count of games in the database.
Future<int> getGameCount() async {
final count =
await (selectOnly(gameTable)..addColumns([gameTable.id.count()]))
.map((row) => row.read(gameTable.id.count()))
.getSingle();
/// By default, only returns non-deleted games.
Future<int> getGameCount({bool includeDeleted = false}) async {
final query = selectOnly(gameTable)..addColumns([gameTable.id.count()]);
if (!includeDeleted) {
query.where(gameTable.deleted.equals(false));
}
final count = await query
.map((row) => row.read(gameTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Checks if a game with the given [gameId] exists in the database.
/// By default, only returns non-deleted games.
/// Returns `true` if the game exists, `false` otherwise.
Future<bool> gameExists({required String gameId}) async {
Future<bool> gameExists({
required String gameId,
bool includeDeleted = false,
}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingleOrNull();
return result != null;
}
/// Retrieves all games from the database.
Future<List<Game>> getAllGames() async {
/// By default, only returns non-deleted games.
Future<List<Game>> getAllGames({bool includeDeleted = false}) async {
final query = select(gameTable);
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.get();
return result
.map(
@@ -101,8 +116,15 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
}
/// Retrieves a [Game] by its [gameId].
Future<Game> getGameById({required String gameId}) async {
/// By default, only returns non-deleted games.
Future<Game> getGameById({
required String gameId,
bool includeDeleted = false,
}) async {
final query = select(gameTable)..where((g) => g.id.equals(gameId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingle();
return Game(
id: result.id,
@@ -196,9 +218,10 @@ class GameDao extends DatabaseAccessor<AppDatabase> with _$GameDaoMixin {
}
/// Retrieves all games with their respective match counts.
/// By default, only returns non-deleted games.
/// Returns a list of tuples (Game, matchCount).
Future<List<(Game, int)>> getGameUsage() async {
final games = await getAllGames();
Future<List<(Game, int)>> getGameUsage({bool includeDeleted = false}) async {
final games = await getAllGames(includeDeleted: includeDeleted);
final results = <(Game, int)>[];

View File

@@ -139,8 +139,12 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
/* Read */
/// Retrieves all groups from the database.
Future<List<Group>> getAllGroups() async {
/// By default, only returns non-deleted groups.
Future<List<Group>> getAllGroups({bool includeDeleted = false}) async {
final query = select(groupTable);
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.get();
return Future.wait(
result.map((groupData) async {
@@ -159,8 +163,15 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
}
/// Retrieves a [Group] by its [groupId], including its members.
Future<Group> getGroupById({required String groupId}) async {
/// By default, only returns non-deleted groups.
Future<Group> getGroupById({
required String groupId,
bool includeDeleted = false,
}) async {
final query = select(groupTable)..where((g) => g.id.equals(groupId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingle();
List<Player> members = await db.playerGroupDao.getPlayersOfGroup(
@@ -177,18 +188,29 @@ class GroupDao extends DatabaseAccessor<AppDatabase> with _$GroupDaoMixin {
}
/// Retrieves the number of groups in the database.
Future<int> getGroupCount() async {
final count =
await (selectOnly(groupTable)..addColumns([groupTable.id.count()]))
.map((row) => row.read(groupTable.id.count()))
.getSingle();
/// By default, only returns non-deleted groups.
Future<int> getGroupCount({bool includeDeleted = false}) async {
final query = selectOnly(groupTable)..addColumns([groupTable.id.count()]);
if (!includeDeleted) {
query.where(groupTable.deleted.equals(false));
}
final count = await query
.map((row) => row.read(groupTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Checks if a group with the given [groupId] exists in the database.
/// By default, only returns non-deleted groups.
/// Returns `true` if the group exists, `false` otherwise.
Future<bool> groupExists({required String groupId}) async {
Future<bool> groupExists({
required String groupId,
bool includeDeleted = false,
}) async {
final query = select(groupTable)..where((g) => g.id.equals(groupId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingleOrNull();
return result != null;
}

View File

@@ -255,25 +255,40 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
/* Read */
/// Checks if a match with the given [matchId] exists in the database.
/// By default, only returns non-deleted matches.
/// Returns `true` if the match exists, otherwise `false`.
Future<bool> matchExists({required String matchId}) async {
Future<bool> matchExists({
required String matchId,
bool includeDeleted = false,
}) async {
final query = select(matchTable)..where((g) => g.id.equals(matchId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingleOrNull();
return result != null;
}
/// Retrieves the number of matches in the database.
Future<int> getMatchCount() async {
final count =
await (selectOnly(matchTable)..addColumns([matchTable.id.count()]))
.map((row) => row.read(matchTable.id.count()))
.getSingle();
/// By default, only returns non-deleted matches.
Future<int> getMatchCount({bool includeDeleted = false}) async {
final query = selectOnly(matchTable)..addColumns([matchTable.id.count()]);
if (!includeDeleted) {
query.where(matchTable.deleted.equals(false));
}
final count = await query
.map((row) => row.read(matchTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Retrieves all matches from the database.
Future<List<Match>> getAllMatches() async {
/// By default, only returns non-deleted matches.
Future<List<Match>> getAllMatches({bool includeDeleted = false}) async {
final query = select(matchTable);
if (!includeDeleted) {
query.where((m) => m.deleted.equals(false));
}
final result = await query.get();
return Future.wait(
@@ -310,8 +325,15 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
}
/// Retrieves a [Match] by its [matchId].
Future<Match> getMatchById({required String matchId}) async {
/// By default, only returns non-deleted matches.
Future<Match> getMatchById({
required String matchId,
bool includeDeleted = false,
}) async {
final query = select(matchTable)..where((g) => g.id.equals(matchId));
if (!includeDeleted) {
query.where((g) => g.deleted.equals(false));
}
final result = await query.getSingle();
final game = await db.gameDao.getGameById(gameId: result.gameId);
@@ -342,20 +364,34 @@ class MatchDao extends DatabaseAccessor<AppDatabase> with _$MatchDaoMixin {
}
/// Retrieves the number of matches associated with a specific game.
Future<int> getMatchCountByGame({required String gameId}) async {
final count =
await (selectOnly(matchTable)
..where(matchTable.gameId.equals(gameId))
..addColumns([matchTable.id.count()]))
.map((row) => row.read(matchTable.id.count()))
.getSingle();
/// By default, only returns non-deleted matches.
Future<int> getMatchCountByGame({
required String gameId,
bool includeDeleted = false,
}) async {
final query = selectOnly(matchTable)
..where(matchTable.gameId.equals(gameId));
if (!includeDeleted) {
query.where(matchTable.deleted.equals(false));
}
query.addColumns([matchTable.id.count()]);
final count = await query
.map((row) => row.read(matchTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Retrieves all matches associated with the given [groupId].
/// By default, only returns non-deleted matches.
/// Queries the database directly, filtering by [groupId].
Future<List<Match>> getMatchesByGroup({required String groupId}) async {
Future<List<Match>> getMatchesByGroup({
required String groupId,
bool includeDeleted = false,
}) async {
final query = select(matchTable)..where((m) => m.groupId.equals(groupId));
if (!includeDeleted) {
query.where((m) => m.deleted.equals(false));
}
final rows = await query.get();
return Future.wait(

View File

@@ -110,25 +110,40 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
/* Read */
/// Retrieves the total count of players in the database.
Future<int> getPlayerCount() async {
final count =
await (selectOnly(playerTable)..addColumns([playerTable.id.count()]))
.map((row) => row.read(playerTable.id.count()))
.getSingle();
/// By default, only returns non-deleted players.
Future<int> getPlayerCount({bool includeDeleted = false}) async {
final query = selectOnly(playerTable)..addColumns([playerTable.id.count()]);
if (!includeDeleted) {
query.where(playerTable.deleted.equals(false));
}
final count = await query
.map((row) => row.read(playerTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Checks if a player with the given [playerId] exists in the database.
/// By default, only returns non-deleted players.
/// Returns `true` if the player exists, `false` otherwise.
Future<bool> playerExists({required String playerId}) async {
Future<bool> playerExists({
required String playerId,
bool includeDeleted = false,
}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
if (!includeDeleted) {
query.where((p) => p.deleted.equals(false));
}
final result = await query.getSingleOrNull();
return result != null;
}
/// Retrieves all players from the database.
Future<List<Player>> getAllPlayers() async {
/// By default, only returns non-deleted players.
Future<List<Player>> getAllPlayers({bool includeDeleted = false}) async {
final query = select(playerTable);
if (!includeDeleted) {
query.where((p) => p.deleted.equals(false));
}
final result = await query.get();
return result
.map(
@@ -144,8 +159,15 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
}
/// Retrieves a [Player] by their [id].
Future<Player> getPlayerById({required String playerId}) async {
/// By default, only returns non-deleted players.
Future<Player> getPlayerById({
required String playerId,
bool includeDeleted = false,
}) async {
final query = select(playerTable)..where((p) => p.id.equals(playerId));
if (!includeDeleted) {
query.where((p) => p.deleted.equals(false));
}
final result = await query.getSingle();
return Player(
id: result.id,
@@ -226,8 +248,15 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
/* Name count management */
/// Retrieves the count of players with the given [name].
Future<int> getNameCount({required String name}) async {
/// By default, only returns non-deleted players.
Future<int> getNameCount({
required String name,
bool includeDeleted = false,
}) async {
final query = select(playerTable)..where((p) => p.name.equals(name));
if (!includeDeleted) {
query.where((p) => p.deleted.equals(false));
}
final result = await query.get();
return result.length;
}
@@ -246,11 +275,17 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
}
@visibleForTesting
Future<Player?> getPlayerWithHighestNameCount({required String name}) async {
Future<Player?> getPlayerWithHighestNameCount({
required String name,
bool includeDeleted = false,
}) async {
final query = select(playerTable)
..where((p) => p.name.equals(name))
..orderBy([(p) => OrderingTerm.desc(p.nameCount)])
..limit(1);
if (!includeDeleted) {
query.where((p) => p.deleted.equals(false));
}
final result = await query.getSingleOrNull();
if (result != null) {
return Player(

View File

@@ -83,25 +83,40 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
/* Read */
/// Retrieves the total count of teams in the database.
Future<int> getTeamCount() async {
final count =
await (selectOnly(teamTable)..addColumns([teamTable.id.count()]))
.map((row) => row.read(teamTable.id.count()))
.getSingle();
/// By default, only returns non-deleted players.
Future<int> getTeamCount({bool includeDeleted = false}) async {
final query = selectOnly(teamTable)..addColumns([teamTable.id.count()]);
if (!includeDeleted) {
query.where(teamTable.deleted.equals(false));
}
final count = await query
.map((row) => row.read(teamTable.id.count()))
.getSingle();
return count ?? 0;
}
/// Checks if a team with the given [teamId] exists in the database.
/// By default, only returns non-deleted players.
/// Returns `true` if the team exists, `false` otherwise.
Future<bool> teamExists({required String teamId}) async {
Future<bool> teamExists({
required String teamId,
bool includeDeleted = false,
}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
if (!includeDeleted) {
query.where((t) => t.deleted.equals(false));
}
final result = await query.getSingleOrNull();
return result != null;
}
/// Retrieves all teams from the database.
Future<List<Team>> getAllTeams() async {
/// By default, only returns non-deleted players.
Future<List<Team>> getAllTeams({bool includeDeleted = false}) async {
final query = select(teamTable);
if (!includeDeleted) {
query.where((t) => t.deleted.equals(false));
}
final result = await query.get();
return Future.wait(
result.map((row) async {
@@ -117,8 +132,15 @@ class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
}
/// Retrieves a [Team] by its [teamId], including its members.
Future<Team> getTeamById({required String teamId}) async {
/// By default, only returns non-deleted players.
Future<Team> getTeamById({
required String teamId,
bool includeDeleted = false,
}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
if (!includeDeleted) {
query.where((t) => t.deleted.equals(false));
}
final result = await query.getSingle();
final members = await _getTeamMembers(teamId: teamId);
return Team(

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@ class GameTable extends Table {
TextColumn get color => text()();
TextColumn get icon => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -5,6 +5,7 @@ class GroupTable extends Table {
TextColumn get name => text()();
TextColumn get description => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -15,6 +15,7 @@ class MatchTable extends Table {
TextColumn get notes => text()();
DateTimeColumn get createdAt => dateTime()();
DateTimeColumn get endedAt => dateTime().nullable()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -6,6 +6,7 @@ class PlayerTable extends Table {
TextColumn get name => text()();
IntColumn get nameCount => integer().withDefault(const Constant(0))();
TextColumn get description => text()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -4,6 +4,7 @@ class TeamTable extends Table {
TextColumn get id => text()();
TextColumn get name => text()();
DateTimeColumn get createdAt => dateTime()();
BoolColumn get deleted => boolean().withDefault(const Constant(false))();
@override
Set<Column<Object>> get primaryKey => {id};

View File

@@ -9,6 +9,7 @@ class Group {
final String description;
final DateTime createdAt;
final List<Player> members;
final bool deleted;
Group({
String? id,
@@ -16,6 +17,7 @@ class Group {
required this.name,
String? description,
required this.members,
this.deleted = false,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(),
description = description ?? '';
@@ -68,7 +70,8 @@ class Group {
createdAt = DateTime.parse(json['createdAt']),
name = json['name'],
description = json['description'],
members = [];
members = [],
deleted = json['deleted'] ?? false;
/// Converts the Group instance to a JSON object. Related [Player] objects are
/// represented by their IDs.
@@ -78,5 +81,6 @@ class Group {
'name': name,
'description': description,
'memberIds': members.map((member) => member.id).toList(),
'deleted': deleted,
};
}

View File

@@ -19,6 +19,7 @@ class Match {
final List<Team>? teams;
final String notes;
Map<String, ScoreEntry?> scores;
final bool deleted;
Match({
required this.name,
@@ -31,6 +32,7 @@ class Match {
String? id,
DateTime? createdAt,
Map<String, ScoreEntry?>? scores,
this.deleted = false,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(),
scores = scores ?? {for (Player p in players) p.id: null};
@@ -123,7 +125,8 @@ class Match {
),
)
: {},
notes = json['notes'] ?? '';
notes = json['notes'] ?? '',
deleted = json['deleted'] ?? false;
Map<String, dynamic> toJson() => {
'id': id,
@@ -136,6 +139,7 @@ class Match {
'teams': teams?.map((team) => team.toJson()).toList(),
'scores': scores.map((key, value) => MapEntry(key, value?.toJson())),
'notes': notes,
'deleted': deleted,
};
List<Player> get mvp {

View File

@@ -7,6 +7,7 @@ class Player {
final String name;
int nameCount;
final String description;
final bool deleted;
Player({
String? id,
@@ -14,6 +15,7 @@ class Player {
required this.name,
this.nameCount = 0,
String? description,
this.deleted = false,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now(),
description = description ?? '';
@@ -58,12 +60,14 @@ class Player {
createdAt = DateTime.parse(json['createdAt']),
name = json['name'],
nameCount = 0,
description = json['description'];
description = json['description'],
deleted = json['deleted'] ?? false;
Map<String, dynamic> toJson() => {
'id': id,
'createdAt': createdAt.toIso8601String(),
'name': name,
'description': description,
'deleted': deleted,
};
}

View File

@@ -2,8 +2,9 @@ class ScoreEntry {
final int roundNumber;
final int score;
final int change;
final bool deleted;
ScoreEntry({required this.score, this.roundNumber = 0, this.change = 0});
ScoreEntry({required this.score, this.roundNumber = 0, this.change = 0, this.deleted = false});
@override
String toString() {
@@ -33,11 +34,13 @@ class ScoreEntry {
ScoreEntry.fromJson(Map<String, dynamic> json)
: roundNumber = json['roundNumber'],
score = json['score'],
change = json['change'];
change = json['change'],
deleted = json['deleted'] ?? false;
Map<String, dynamic> toJson() => {
'roundNumber': roundNumber,
'score': score,
'change': change,
'deleted': deleted,
};
}

View File

@@ -8,12 +8,14 @@ class Team {
final String name;
final DateTime createdAt;
final List<Player> members;
final bool deleted;
Team({
String? id,
required this.name,
DateTime? createdAt,
required this.members,
this.deleted = false,
}) : id = id ?? const Uuid().v4(),
createdAt = createdAt ?? clock.now();
@@ -58,12 +60,14 @@ class Team {
: id = json['id'],
name = json['name'],
createdAt = DateTime.parse(json['createdAt']),
members = []; // Populated during import via DataTransferService
members = [],
deleted = json['deleted'] ?? false;
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
'createdAt': createdAt.toIso8601String(),
'memberIds': members.map((member) => member.id).toList(),
'deleted': deleted,
};
}