7 Commits

Author SHA1 Message Date
gelbeinhalb
b9b6ff85ea update g files
Some checks failed
Pull Request Pipeline / test (pull_request) Failing after 2m5s
Pull Request Pipeline / lint (pull_request) Failing after 2m9s
2026-01-16 12:38:25 +01:00
gelbeinhalb
52c1605ce9 update database.dart 2026-01-16 12:38:16 +01:00
gelbeinhalb
c2a9bda8b1 add team_dao.dart 2026-01-16 12:38:09 +01:00
gelbeinhalb
a13d9c55ea add score_dao.dart 2026-01-16 12:37:37 +01:00
gelbeinhalb
b737cae356 remove group_match_dao.dart 2026-01-16 12:27:20 +01:00
gelbeinhalb
3857665444 update player_match_dao.dart 2026-01-16 12:23:24 +01:00
gelbeinhalb
4b900c12bf add description to player 2026-01-16 12:22:23 +01:00
12 changed files with 3552 additions and 849 deletions

View File

@@ -0,0 +1,8 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'game_dao.dart';
// ignore_for_file: type=lint
mixin _$GameDaoMixin on DatabaseAccessor<AppDatabase> {
$GameTableTable get gameTable => attachedDatabase.gameTable;
}

View File

@@ -1,108 +0,0 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/group_match_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/dto/group.dart';
part 'group_match_dao.g.dart';
@DriftAccessor(tables: [GroupMatchTable, GroupTable])
class GroupMatchDao extends DatabaseAccessor<AppDatabase>
with _$GroupMatchDaoMixin {
GroupMatchDao(super.db);
/// Adds a group to a match by inserting a record into the [GroupMatchTable].
Future<void> addGroupToMatch({
required String matchId,
required String groupId,
}) async {
await into(groupMatchTable).insert(
GroupMatchTableCompanion.insert(
matchId: matchId,
groupId: groupId,
),
mode: InsertMode.insertOrIgnore,
);
}
/// Retrieves the [Group] associated with the given [matchId].
/// Returns null if no group is found.
Future<Group?> getGroupOfMatch({required String matchId}) async {
final query = select(groupMatchTable)
..where((gm) => gm.matchId.equals(matchId));
final result = await query.getSingleOrNull();
if (result == null) return null;
return db.groupDao.getGroupById(groupId: result.groupId);
}
/// Checks if a match has a group associated with it.
/// Returns `true` if the match has a group, otherwise `false`.
Future<bool> matchHasGroup({required String matchId}) async {
final count = await (selectOnly(groupMatchTable)
..where(groupMatchTable.matchId.equals(matchId))
..addColumns([groupMatchTable.groupId.count()]))
.map((row) => row.read(groupMatchTable.groupId.count()))
.getSingle();
return (count ?? 0) > 0;
}
/// Checks if a specific group is associated with a specific match.
/// Returns `true` if the group is in the match, otherwise `false`.
Future<bool> isGroupInMatch({
required String matchId,
required String groupId,
}) async {
final query = select(groupMatchTable)
..where(
(gm) => gm.matchId.equals(matchId) & gm.groupId.equals(groupId),
);
final result = await query.getSingleOrNull();
return result != null;
}
/// Removes the association of a group with a match by deleting the record
/// from the [GroupMatchTable].
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> removeGroupFromMatch({
required String matchId,
required String groupId,
}) async {
final query = delete(groupMatchTable)
..where((gm) => gm.matchId.equals(matchId) & gm.groupId.equals(groupId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Updates the group associated with a match.
/// Removes the existing group association and adds the new one.
Future<void> updateGroupOfMatch({
required String matchId,
required String newGroupId,
}) async {
await db.transaction(() async {
// Remove existing group association
await (delete(groupMatchTable)
..where((gm) => gm.matchId.equals(matchId)))
.go();
// Add new group association
await into(groupMatchTable).insert(
GroupMatchTableCompanion.insert(
matchId: matchId,
groupId: newGroupId,
),
);
});
}
/// Retrieves all matches associated with a specific group.
Future<List<String>> getMatchIdsForGroup({required String groupId}) async {
final query = select(groupMatchTable)
..where((gm) => gm.groupId.equals(groupId));
final result = await query.get();
return result.map((row) => row.matchId).toList();
}
}

View File

@@ -4,5 +4,11 @@ part of 'match_dao.dart';
// ignore_for_file: type=lint
mixin _$MatchDaoMixin on DatabaseAccessor<AppDatabase> {
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$TeamTableTable get teamTable => attachedDatabase.teamTable;
$PlayerMatchTableTable get playerMatchTable =>
attachedDatabase.playerMatchTable;
}

View File

@@ -15,7 +15,12 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
final result = await query.get();
return result
.map(
(row) => Player(id: row.id, name: row.name, createdAt: row.createdAt),
(row) => Player(
id: row.id,
name: row.name,
description: row.description,
createdAt: row.createdAt,
),
)
.toList();
}
@@ -27,6 +32,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
return Player(
id: result.id,
name: result.name,
description: result.description,
createdAt: result.createdAt,
);
}
@@ -40,6 +46,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
PlayerTableCompanion.insert(
id: player.id,
name: player.name,
description: Value(player.description),
createdAt: player.createdAt,
),
mode: InsertMode.insertOrReplace,
@@ -63,6 +70,7 @@ class PlayerDao extends DatabaseAccessor<AppDatabase> with _$PlayerDaoMixin {
(player) => PlayerTableCompanion.insert(
id: player.id,
name: player.name,
description: Value(player.description),
createdAt: player.createdAt,
),
)

View File

@@ -1,23 +1,31 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/player_match_table.dart';
import 'package:game_tracker/data/db/tables/team_table.dart';
import 'package:game_tracker/data/dto/player.dart';
part 'player_match_dao.g.dart';
@DriftAccessor(tables: [PlayerMatchTable])
@DriftAccessor(tables: [PlayerMatchTable, TeamTable])
class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
with _$PlayerMatchDaoMixin {
PlayerMatchDao(super.db);
/// Associates a player with a match by inserting a record into the
/// [PlayerMatchTable].
/// [PlayerMatchTable]. Optionally associates with a team and sets initial score.
Future<void> addPlayerToMatch({
required String matchId,
required String playerId,
String? teamId,
int score = 0,
}) async {
await into(playerMatchTable).insert(
PlayerMatchTableCompanion.insert(playerId: playerId, matchId: matchId),
PlayerMatchTableCompanion.insert(
playerId: playerId,
matchId: matchId,
teamId: Value(teamId),
score: score,
),
mode: InsertMode.insertOrIgnore,
);
}
@@ -38,6 +46,50 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
return players;
}
/// Retrieves a player's score for a specific match.
/// Returns null if the player is not in the match.
Future<int?> getPlayerScore({
required String matchId,
required String playerId,
}) async {
final result = await (select(playerMatchTable)
..where(
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
))
.getSingleOrNull();
return result?.score;
}
/// Updates the score for a player in a match.
/// Returns `true` if the update was successful, otherwise `false`.
Future<bool> updatePlayerScore({
required String matchId,
required String playerId,
required int newScore,
}) async {
final rowsAffected = await (update(playerMatchTable)
..where(
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
))
.write(PlayerMatchTableCompanion(score: Value(newScore)));
return rowsAffected > 0;
}
/// Updates the team for a player in a match.
/// Returns `true` if the update was successful, otherwise `false`.
Future<bool> updatePlayerTeam({
required String matchId,
required String playerId,
required String? teamId,
}) async {
final rowsAffected = await (update(playerMatchTable)
..where(
(p) => p.matchId.equals(matchId) & p.playerId.equals(playerId),
))
.write(PlayerMatchTableCompanion(teamId: Value(teamId)));
return rowsAffected > 0;
}
/// Checks if there are any players associated with the given [matchId].
/// Returns `true` if there are players, otherwise `false`.
Future<bool> matchHasPlayers({required String matchId}) async {
@@ -114,6 +166,7 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
(id) => PlayerMatchTableCompanion.insert(
playerId: id,
matchId: matchId,
score: 0,
),
)
.toList();
@@ -127,4 +180,23 @@ class PlayerMatchDao extends DatabaseAccessor<AppDatabase>
}
});
}
/// Retrieves all players in a specific team for a match.
Future<List<Player>> getPlayersInTeam({
required String matchId,
required String teamId,
}) async {
final result = await (select(playerMatchTable)
..where(
(p) => p.matchId.equals(matchId) & p.teamId.equals(teamId),
))
.get();
if (result.isEmpty) return [];
final futures = result.map(
(row) => db.playerDao.getPlayerById(playerId: row.playerId),
);
return Future.wait(futures);
}
}

View File

@@ -5,7 +5,10 @@ part of 'player_match_dao.dart';
// ignore_for_file: type=lint
mixin _$PlayerMatchDaoMixin on DatabaseAccessor<AppDatabase> {
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$TeamTableTable get teamTable => attachedDatabase.teamTable;
$PlayerMatchTableTable get playerMatchTable =>
attachedDatabase.playerMatchTable;
}

191
lib/data/dao/score_dao.dart Normal file
View File

@@ -0,0 +1,191 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/score_table.dart';
part 'score_dao.g.dart';
/// A data class representing a score entry.
class ScoreEntry {
final String playerId;
final String matchId;
final int roundNumber;
final int score;
final int change;
ScoreEntry({
required this.playerId,
required this.matchId,
required this.roundNumber,
required this.score,
required this.change,
});
}
@DriftAccessor(tables: [ScoreTable])
class ScoreDao extends DatabaseAccessor<AppDatabase> with _$ScoreDaoMixin {
ScoreDao(super.db);
/// Adds a score entry to the database.
Future<void> addScore({
required String playerId,
required String matchId,
required int roundNumber,
required int score,
required int change,
}) async {
await into(scoreTable).insert(
ScoreTableCompanion.insert(
playerId: playerId,
matchId: matchId,
roundNumber: roundNumber,
score: score,
change: change,
),
mode: InsertMode.insertOrReplace,
);
}
/// Retrieves all scores for a specific match.
Future<List<ScoreEntry>> getScoresForMatch({required String matchId}) async {
final query = select(scoreTable)..where((s) => s.matchId.equals(matchId));
final result = await query.get();
return result
.map(
(row) => ScoreEntry(
playerId: row.playerId,
matchId: row.matchId,
roundNumber: row.roundNumber,
score: row.score,
change: row.change,
),
)
.toList();
}
/// Retrieves all scores for a specific player in a match.
Future<List<ScoreEntry>> getPlayerScoresInMatch({
required String playerId,
required String matchId,
}) async {
final query = select(scoreTable)
..where(
(s) => s.playerId.equals(playerId) & s.matchId.equals(matchId),
)
..orderBy([(s) => OrderingTerm.asc(s.roundNumber)]);
final result = await query.get();
return result
.map(
(row) => ScoreEntry(
playerId: row.playerId,
matchId: row.matchId,
roundNumber: row.roundNumber,
score: row.score,
change: row.change,
),
)
.toList();
}
/// Retrieves the score for a specific round.
Future<ScoreEntry?> getScoreForRound({
required String playerId,
required String matchId,
required int roundNumber,
}) async {
final query = select(scoreTable)
..where(
(s) =>
s.playerId.equals(playerId) &
s.matchId.equals(matchId) &
s.roundNumber.equals(roundNumber),
);
final result = await query.getSingleOrNull();
if (result == null) return null;
return ScoreEntry(
playerId: result.playerId,
matchId: result.matchId,
roundNumber: result.roundNumber,
score: result.score,
change: result.change,
);
}
/// Updates a score entry.
Future<bool> updateScore({
required String playerId,
required String matchId,
required int roundNumber,
required int newScore,
required int newChange,
}) async {
final rowsAffected = await (update(scoreTable)
..where(
(s) =>
s.playerId.equals(playerId) &
s.matchId.equals(matchId) &
s.roundNumber.equals(roundNumber),
))
.write(
ScoreTableCompanion(
score: Value(newScore),
change: Value(newChange),
),
);
return rowsAffected > 0;
}
/// Deletes a score entry.
Future<bool> deleteScore({
required String playerId,
required String matchId,
required int roundNumber,
}) async {
final query = delete(scoreTable)
..where(
(s) =>
s.playerId.equals(playerId) &
s.matchId.equals(matchId) &
s.roundNumber.equals(roundNumber),
);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Deletes all scores for a specific match.
Future<bool> deleteScoresForMatch({required String matchId}) async {
final query = delete(scoreTable)..where((s) => s.matchId.equals(matchId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Deletes all scores for a specific player.
Future<bool> deleteScoresForPlayer({required String playerId}) async {
final query = delete(scoreTable)..where((s) => s.playerId.equals(playerId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Gets the latest round number for a match.
Future<int> getLatestRoundNumber({required String matchId}) async {
final query = selectOnly(scoreTable)
..where(scoreTable.matchId.equals(matchId))
..addColumns([scoreTable.roundNumber.max()]);
final result = await query.getSingle();
return result.read(scoreTable.roundNumber.max()) ?? 0;
}
/// Gets the total score for a player in a match (sum of all changes).
Future<int> getTotalScoreForPlayer({
required String playerId,
required String matchId,
}) async {
final scores = await getPlayerScoresInMatch(
playerId: playerId,
matchId: matchId,
);
if (scores.isEmpty) return 0;
// Return the score from the latest round
return scores.last.score;
}
}

View File

@@ -0,0 +1,12 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'score_dao.dart';
// ignore_for_file: type=lint
mixin _$ScoreDaoMixin on DatabaseAccessor<AppDatabase> {
$PlayerTableTable get playerTable => attachedDatabase.playerTable;
$GameTableTable get gameTable => attachedDatabase.gameTable;
$GroupTableTable get groupTable => attachedDatabase.groupTable;
$MatchTableTable get matchTable => attachedDatabase.matchTable;
$ScoreTableTable get scoreTable => attachedDatabase.scoreTable;
}

145
lib/data/dao/team_dao.dart Normal file
View File

@@ -0,0 +1,145 @@
import 'package:drift/drift.dart';
import 'package:game_tracker/data/db/database.dart';
import 'package:game_tracker/data/db/tables/team_table.dart';
import 'package:game_tracker/data/dto/player.dart';
import 'package:game_tracker/data/dto/team.dart';
part 'team_dao.g.dart';
@DriftAccessor(tables: [TeamTable])
class TeamDao extends DatabaseAccessor<AppDatabase> with _$TeamDaoMixin {
TeamDao(super.db);
/// Retrieves all teams from the database.
/// Note: This returns teams without their members. Use getTeamById for full team data.
Future<List<Team>> getAllTeams() async {
final query = select(teamTable);
final result = await query.get();
return Future.wait(
result.map((row) async {
final members = await _getTeamMembers(teamId: row.id);
return Team(
id: row.id,
createdAt: row.createdAt,
members: members,
);
}),
);
}
/// Retrieves a [Team] by its [teamId], including its members.
Future<Team> getTeamById({required String teamId}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
final result = await query.getSingle();
final members = await _getTeamMembers(teamId: teamId);
return Team(
id: result.id,
createdAt: result.createdAt,
members: members,
);
}
/// Helper method to get team members from player_match_table.
/// This assumes team members are tracked via the player_match_table.
Future<List<Player>> _getTeamMembers({required String teamId}) async {
// Get all player_match entries with this teamId
final playerMatchQuery = select(db.playerMatchTable)
..where((pm) => pm.teamId.equals(teamId));
final playerMatches = await playerMatchQuery.get();
if (playerMatches.isEmpty) return [];
// Get unique player IDs
final playerIds = playerMatches.map((pm) => pm.playerId).toSet();
// Fetch all players
final players = await Future.wait(
playerIds.map((id) => db.playerDao.getPlayerById(playerId: id)),
);
return players;
}
/// Adds a new [team] to the database.
/// Returns `true` if the team was added, `false` otherwise.
Future<bool> addTeam({required Team team}) async {
if (!await teamExists(teamId: team.id)) {
await into(teamTable).insert(
TeamTableCompanion.insert(
id: team.id,
name: '', // Team name from table (not in DTO currently)
createdAt: team.createdAt,
),
mode: InsertMode.insertOrReplace,
);
return true;
}
return false;
}
/// Adds multiple [teams] to the database in a batch operation.
Future<bool> addTeamsAsList({required List<Team> teams}) async {
if (teams.isEmpty) return false;
await db.batch(
(b) => b.insertAll(
teamTable,
teams
.map(
(team) => TeamTableCompanion.insert(
id: team.id,
name: '',
createdAt: team.createdAt,
),
)
.toList(),
mode: InsertMode.insertOrIgnore,
),
);
return true;
}
/// Deletes the team with the given [teamId] from the database.
/// Returns `true` if the team was deleted, `false` otherwise.
Future<bool> deleteTeam({required String teamId}) async {
final query = delete(teamTable)..where((t) => t.id.equals(teamId));
final rowsAffected = await query.go();
return rowsAffected > 0;
}
/// Checks if a team with the given [teamId] exists in the database.
/// Returns `true` if the team exists, `false` otherwise.
Future<bool> teamExists({required String teamId}) async {
final query = select(teamTable)..where((t) => t.id.equals(teamId));
final result = await query.getSingleOrNull();
return result != null;
}
/// Updates the name of the team with the given [teamId].
Future<void> updateTeamName({
required String teamId,
required String newName,
}) async {
await (update(teamTable)..where((t) => t.id.equals(teamId))).write(
TeamTableCompanion(name: Value(newName)),
);
}
/// 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();
return count ?? 0;
}
/// Deletes all teams from the database.
/// Returns `true` if more than 0 rows were affected, otherwise `false`.
Future<bool> deleteAllTeams() async {
final query = delete(teamTable);
final rowsAffected = await query.go();
return rowsAffected > 0;
}
}

View File

@@ -0,0 +1,8 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'team_dao.dart';
// ignore_for_file: type=lint
mixin _$TeamDaoMixin on DatabaseAccessor<AppDatabase> {
$TeamTableTable get teamTable => attachedDatabase.teamTable;
}

View File

@@ -1,15 +1,21 @@
import 'package:drift/drift.dart';
import 'package:drift_flutter/drift_flutter.dart';
import 'package:game_tracker/data/dao/game_dao.dart';
import 'package:game_tracker/data/dao/group_dao.dart';
import 'package:game_tracker/data/dao/match_dao.dart';
import 'package:game_tracker/data/dao/player_dao.dart';
import 'package:game_tracker/data/dao/player_group_dao.dart';
import 'package:game_tracker/data/dao/player_match_dao.dart';
import 'package:game_tracker/data/dao/score_dao.dart';
import 'package:game_tracker/data/dao/team_dao.dart';
import 'package:game_tracker/data/db/tables/game_table.dart';
import 'package:game_tracker/data/db/tables/group_table.dart';
import 'package:game_tracker/data/db/tables/match_table.dart';
import 'package:game_tracker/data/db/tables/player_group_table.dart';
import 'package:game_tracker/data/db/tables/player_match_table.dart';
import 'package:game_tracker/data/db/tables/player_table.dart';
import 'package:game_tracker/data/db/tables/score_table.dart';
import 'package:game_tracker/data/db/tables/team_table.dart';
import 'package:path_provider/path_provider.dart';
part 'database.g.dart';
@@ -18,23 +24,29 @@ part 'database.g.dart';
tables: [
PlayerTable,
GroupTable,
GameTable,
TeamTable,
MatchTable,
PlayerGroupTable,
PlayerMatchTable
PlayerMatchTable,
ScoreTable,
],
daos: [
PlayerDao,
GroupDao,
GameDao,
TeamDao,
MatchDao,
PlayerGroupDao,
PlayerMatchDao
PlayerMatchDao,
ScoreDao,
],
)
class AppDatabase extends _$AppDatabase {
AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection());
@override
int get schemaVersion => 1;
int get schemaVersion => 2;
@override
MigrationStrategy get migration {

File diff suppressed because it is too large Load Diff