Initial commit: Flutter app + PHP/MySQL backend on Hostinger

Replaces Firebase with a self-hosted PHP/MySQL API served from
winded.prymsolutions.com. Includes full backend (schema, auth, events,
teams, brackets, suggestions, stats, media, file upload) and updated
Flutter repositories and domain models.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 20:13:57 -07:00
commit b239ae3e5f
208 changed files with 19187 additions and 0 deletions
@@ -0,0 +1,69 @@
import 'package:riverpod_annotation/riverpod_annotation.dart';
import '../../teams/domain/player.dart';
import '../../teams/domain/team.dart';
import '../../teams/infrastructure/teams_repository.dart';
part 'stats_notifier.g.dart';
/// A player paired with the team they belong to. Records are emitted by the
/// stats providers so leaderboard rows can show "Player — Team" without doing
/// a second lookup.
typedef PlayerWithTeam = ({Player player, Team team});
/// Top scorers across every team, sorted by goals scored (descending). Ties
/// are broken by assists, then by player name so the order is deterministic.
@riverpod
Future<List<PlayerWithTeam>> topScorers(TopScorersRef ref) async {
final teams = await ref.watch(teamsStreamProvider.future);
final entries = <PlayerWithTeam>[
for (final team in teams)
for (final player in team.players) (player: player, team: team),
];
entries.sort((a, b) {
final byGoals = b.player.goalsScored.compareTo(a.player.goalsScored);
if (byGoals != 0) return byGoals;
final byAssists = b.player.assists.compareTo(a.player.assists);
if (byAssists != 0) return byAssists;
return a.player.name.compareTo(b.player.name);
});
return entries;
}
/// Top assisters across every team, sorted by assists (descending). Ties are
/// broken by goals, then by player name.
@riverpod
Future<List<PlayerWithTeam>> topAssisters(TopAssistersRef ref) async {
final teams = await ref.watch(teamsStreamProvider.future);
final entries = <PlayerWithTeam>[
for (final team in teams)
for (final player in team.players) (player: player, team: team),
];
entries.sort((a, b) {
final byAssists = b.player.assists.compareTo(a.player.assists);
if (byAssists != 0) return byAssists;
final byGoals = b.player.goalsScored.compareTo(a.player.goalsScored);
if (byGoals != 0) return byGoals;
return a.player.name.compareTo(b.player.name);
});
return entries;
}
/// League standings: teams sorted by wins (desc), then draws (desc), then by
/// fewer losses, then name. The points column shown in the UI is computed as
/// `wins * 3 + draws`.
@riverpod
Future<List<Team>> teamStandings(TeamStandingsRef ref) async {
final teams = await ref.watch(teamsStreamProvider.future);
final sorted = [...teams];
sorted.sort((a, b) {
final byWins = b.wins.compareTo(a.wins);
if (byWins != 0) return byWins;
final byDraws = b.draws.compareTo(a.draws);
if (byDraws != 0) return byDraws;
final byLosses = a.losses.compareTo(b.losses);
if (byLosses != 0) return byLosses;
return a.name.compareTo(b.name);
});
return sorted;
}
@@ -0,0 +1,73 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'stats_notifier.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$topScorersHash() => r'217ba2c980b0ac979f18b59f6093ba51ba8ab8d2';
/// Top scorers across every team, sorted by goals scored (descending). Ties
/// are broken by assists, then by player name so the order is deterministic.
///
/// Copied from [topScorers].
@ProviderFor(topScorers)
final topScorersProvider =
AutoDisposeFutureProvider<List<PlayerWithTeam>>.internal(
topScorers,
name: r'topScorersProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$topScorersHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef TopScorersRef = AutoDisposeFutureProviderRef<List<PlayerWithTeam>>;
String _$topAssistersHash() => r'2f95133f5b72f4e1ae7001e01e6bd57856d04ad4';
/// Top assisters across every team, sorted by assists (descending). Ties are
/// broken by goals, then by player name.
///
/// Copied from [topAssisters].
@ProviderFor(topAssisters)
final topAssistersProvider =
AutoDisposeFutureProvider<List<PlayerWithTeam>>.internal(
topAssisters,
name: r'topAssistersProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$topAssistersHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef TopAssistersRef = AutoDisposeFutureProviderRef<List<PlayerWithTeam>>;
String _$teamStandingsHash() => r'644f974075e26a852b073c7bc155a38bb59045d0';
/// League standings: teams sorted by wins (desc), then draws (desc), then by
/// fewer losses, then name. The points column shown in the UI is computed as
/// `wins * 3 + draws`.
///
/// Copied from [teamStandings].
@ProviderFor(teamStandings)
final teamStandingsProvider = AutoDisposeFutureProvider<List<Team>>.internal(
teamStandings,
name: r'teamStandingsProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$teamStandingsHash,
dependencies: null,
allTransitiveDependencies: null,
);
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
typedef TeamStandingsRef = AutoDisposeFutureProviderRef<List<Team>>;
// ignore_for_file: type=lint
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package