Sube capa Infrastructure, Solution y archivos de configuracion del Backend

This commit is contained in:
FrancoRu
2026-09-18 13:36:47 -03:00
parent 2eb4eeef03
commit 1ef9a0bd03
205 changed files with 77651 additions and 0 deletions
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for AuditLog entries, inheriting generic CRUD from GenericRepository.
/// </summary>
/// <param name="context">The application's database context.</param>
public class AuditLogRepository(ApplicationDBContext context)
: GenericRepository<AuditLog>(context), IAuditLogRepository
{
}
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for BlogPost entities, inheriting generic CRUD and implementing IBlogPostRepository.
/// </summary>
/// <param name="context">The application's database context used for data access operations.</param>
public class BlogPostRepository(ApplicationDBContext context)
: GenericRepository<BlogPost>(context), IBlogPostRepository
{
}
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Club entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class ClubRepository(ApplicationDBContext context)
: GenericRepository<Club>(context), IClubRepository
{ }
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Division entities, inheriting generic CRUD and implementing IDivisionRepository.
/// </summary>
public class DivisionRepository(ApplicationDBContext context)
: GenericRepository<Division>(context), IDivisionRepository
{
}
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing DivisionTeamRegistration entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class DivisionTeamRegistrationRepository(ApplicationDBContext context)
: GenericRepository<DivisionTeamRegistration>(context), IDivisionTeamRegistrationRepository
{ }
@@ -0,0 +1,143 @@
using Application.DTOs.Abstract.Request;
using Application.Interfaces.Repositories;
using Application.Utils.Extensions;
using Domain.Entities.Models;
using Infrastructure.Persistance;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
namespace Infrastructure.Repositories;
/// <summary>
/// Provides a generic repository implementation for CRUD operations and querying entities in the database.
/// </summary>
/// <typeparam name="TEntity">
/// The entity type managed by the repository. Must inherit from EntityBase.
/// </typeparam>
public class GenericRepository<TEntity>(ApplicationDBContext context)
: IGenericRepository<TEntity> where TEntity : EntityBase
{
/// <summary>
/// The database context used for data access.
/// </summary>
protected readonly ApplicationDBContext _context = context;
/// <summary>
/// The DbSet{TEntity} representing the collection of entities.
/// </summary>
protected readonly DbSet<TEntity> _dbSet = context.Set<TEntity>();
///<inheritdoc />
public virtual async Task UpdateRangeAsync(IEnumerable<TEntity> entities)
{
_dbSet.UpdateRange(entities);
await _context.SaveChangesAsync();
}
///<inheritdoc />
public virtual async Task UpdateAsync(TEntity entity)
{
_dbSet.Update(entity);
await _context.SaveChangesAsync();
}
///<inheritdoc />
public virtual async Task<TEntity?> GetByIdAsync(Guid id, IEnumerable<Expression<Func<TEntity, object>>>? includes = null)
{
IQueryable<TEntity> query = _dbSet;
foreach (Expression<Func<TEntity, object>> include in includes ?? [])
{
query = query.Include(include);
}
return await query.FirstOrDefaultAsync(e => e.Id == id);
}
///<inheritdoc />
public virtual IQueryable<TEntity> GetQueryable()
{
return _dbSet;
}
///<inheritdoc />
public virtual async Task<IEnumerable<TEntity>> GetAllAsync()
{
return await _dbSet.ToListAsync();
}
///<inheritdoc />
public virtual async Task<IEnumerable<TEntity>> FindAsync(
Expression<Func<TEntity, bool>> predicate,
IEnumerable<Expression<Func<TEntity, object>>>? includes = null,
PaginatedFilterRequest? filter = null,
bool asSplitQuery = false)
{
IQueryable<TEntity> query = _dbSet.Where(predicate);
foreach (Expression<Func<TEntity, object>> include in includes ?? [])
{
query = query.Include(include);
}
if (asSplitQuery)
{
query = query.AsSplitQuery();
}
if (filter != null)
{
query = query.SortBy(filter).Paginate(filter.PageNumber, filter.PageSize);
}
return await query.ToListAsync();
}
///<inheritdoc />
public virtual async Task<TEntity> AddAsync(TEntity entity)
{
await _dbSet.AddAsync(entity);
await _context.SaveChangesAsync();
return entity;
}
///<inheritdoc />
public virtual async Task<IEnumerable<TEntity>> AddRangeAsync(IEnumerable<TEntity> entities)
{
await _dbSet.AddRangeAsync(entities);
await _context.SaveChangesAsync();
return entities;
}
///<inheritdoc />
public virtual void Remove(TEntity entity)
{
_dbSet.Remove(entity);
}
///<inheritdoc />
public virtual async Task<int> RemoveAsync(Expression<Func<TEntity, bool>> expression)
{
return await _dbSet.Where(expression).ExecuteDeleteAsync();
}
///<inheritdoc />
public virtual async Task<int> CountAsync(Expression<Func<TEntity, bool>>? predicate = null)
{
return predicate == null ? await _dbSet.CountAsync() : await _dbSet.CountAsync(predicate);
}
///<inheritdoc />
public virtual async Task<bool> ExistsAsync(Expression<Func<TEntity, bool>> predicate)
{
return await _dbSet.AnyAsync(predicate);
}
}
@@ -0,0 +1,45 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Match entities, inheriting generic CRUD and implementing IMatchRepository.
/// </summary>
/// <param name="context">The application's database context used for data access.</param>
public class MatchRepository(ApplicationDBContext context)
: GenericRepository<Match>(context), IMatchRepository
{
/// <inheritdoc />
public async Task<Match?> GetDetailByIdOrSlugAsync(string idOrSlug)
{
IQueryable<Match> query = _context.Set<Match>()
.Include(match => match.HomeTeam)
.ThenInclude(team => team!.Players)
.ThenInclude(player => player.PlayerTeamRegistrations)
.Include(match => match.VisitorTeam)
.ThenInclude(team => team!.Players)
.ThenInclude(player => player.PlayerTeamRegistrations)
.Include(match => match.Venue)
// Stage → Division gives the match's tournament, used to pick each scorer's jersey number from that tournament's roster registration.
.Include(match => match.Stage)
.ThenInclude(stage => stage.Division)
.Include(match => match.Scorers)
.ThenInclude(scorer => scorer.Player)
.ThenInclude(player => player!.PlayerTeamRegistrations)
.AsSplitQuery();
return Guid.TryParse(idOrSlug, out Guid matchId)
? await query.FirstOrDefaultAsync(match => match.Id == matchId)
: await query.FirstOrDefaultAsync(match => match.Slug == idOrSlug);
}
}
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing MatchSeries entities, inheriting generic CRUD and implementing IMatchSeriesRepository.
/// </summary>
/// <param name="context">The application's database context used for data access.</param>
public class MatchSeriesRepository(ApplicationDBContext context)
: GenericRepository<MatchSeries>(context), IMatchSeriesRepository
{
}
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Player entities, inheriting generic CRUD and implementing IPlayerRepository.
/// </summary>
public class PlayerRepository(ApplicationDBContext context)
: GenericRepository<Player>(context), IPlayerRepository
{
}
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing PlayerSanction entities, inheriting generic CRUD and implementing IPlayerSanctionRepository.
/// </summary>
public class PlayerSanctionRepository(ApplicationDBContext context)
: GenericRepository<PlayerSanction>(context), IPlayerSanctionRepository
{
}
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing PlayerStatistic entities, inheriting generic CRUD and implementing IPlayerStatisticRepository.
/// </summary>
public class PlayerStatisticRepository(ApplicationDBContext context)
: GenericRepository<PlayerStatistic>(context), IPlayerStatisticRepository
{
}
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing PlayerTeamRegistration entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class PlayerTeamRegistrationRepository(ApplicationDBContext context)
: GenericRepository<PlayerTeamRegistration>(context), IPlayerTeamRegistrationRepository
{ }
@@ -0,0 +1,144 @@
using Application.DTOs.Scorer.Request;
using Application.DTOs.Scorer.Response;
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Domain.Enums;
using Infrastructure.Persistance;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for the top-scorers "goleadores" ranking, built from PlayerStatistic rows instead of the Scorer table.
/// </summary>
public class ScorerRepository(ApplicationDBContext context)
: GenericRepository<Scorer>(context), IScorerRepository
{
/// <summary>
/// Builds the top-scorers ranking by summing each player's Points-type PlayerStatistic rows instead of reading the Scorer table, keeping it in sync with the per-match loading path.
/// </summary>
public async Task<(IEnumerable<ScorerByPlayerResponse> Items, int TotalCount)> GetPlayerScoresAsync(GetScorerFilteredRequest filter)
{
IQueryable<Player> playersQuery = _context.Set<Player>();
if (filter.TournamentId.HasValue)
{
playersQuery = playersQuery.Where(p => p.Team.TournamentId == filter.TournamentId.Value);
}
if (filter.MatchId.HasValue)
{
Guid matchId = filter.MatchId.Value;
playersQuery = playersQuery.Where(p =>
_context.Set<Match>().Any(m => m.Id == matchId &&
(m.HomeTeamId == p.TeamId || m.VisitorTeamId == p.TeamId)));
}
if (filter.TeamId.HasValue)
{
playersQuery = playersQuery.Where(p => p.TeamId == filter.TeamId.Value);
}
if (filter.PlayerId.HasValue)
{
playersQuery = playersQuery.Where(p => p.Id == filter.PlayerId.Value);
}
if (filter.DivisionId.HasValue)
{
Guid divisionId = filter.DivisionId.Value;
playersQuery = playersQuery.Where(p =>
_context.Set<StageTeamMatch>().Any(stm => stm.TeamId == p.TeamId && stm.Stage!.DivisionId == divisionId));
}
if (filter.StageId.HasValue)
{
Guid stageId = filter.StageId.Value;
playersQuery = playersQuery.Where(p =>
_context.Set<StageTeamMatch>().Any(stm => stm.TeamId == p.TeamId && stm.StageId == stageId));
}
// The goleadores ranking aggregates from PlayerStatistic, the same table the per-match loading path writes to, so there is no orphan Scorer write-gap and the ranking reflects real loaded points; only Points-type statistics count toward it.
IQueryable<PlayerStatistic> scoreStatsQuery = _context.Set<PlayerStatistic>()
.Where(s => s.Type == StatisticType.Points);
if (filter.TournamentId.HasValue)
{
scoreStatsQuery = scoreStatsQuery.Where(s => s.Match!.Stage.Division.TournamentId == filter.TournamentId.Value);
}
if (filter.DivisionId.HasValue)
{
scoreStatsQuery = scoreStatsQuery.Where(s => s.Match!.Stage.DivisionId == filter.DivisionId.Value);
}
if (filter.StageId.HasValue)
{
scoreStatsQuery = scoreStatsQuery.Where(s => s.Match!.StageId == filter.StageId.Value);
}
if (filter.MatchId.HasValue)
{
scoreStatsQuery = scoreStatsQuery.Where(s => s.MatchId == filter.MatchId.Value);
}
// A season is the calendar year of a tournament's StartDate, and grouping is safe by PlayerId because Player.DocumentNumber is uniquely indexed, so one real person is exactly one Player row reused across seasons rather than duplicated per season.
if (filter.Season.HasValue)
{
int season = filter.Season.Value;
scoreStatsQuery = scoreStatsQuery.Where(s =>
_context.Set<Tournament>().Any(t =>
t.Id == s.Match!.Stage.Division.TournamentId && t.StartDate.Year == season));
playersQuery = playersQuery.Where(p => scoreStatsQuery.Any(s => s.PlayerId == p.Id));
}
IQueryable<ScorerByPlayerResponse> query = playersQuery.Select(player => new ScorerByPlayerResponse
{
PlayerId = player.Id,
FullName = (player.SecondName == null || player.SecondName == "")
? player.LastName.ToUpper() + " " + player.FirstName
: player.LastName.ToUpper() + " " + player.FirstName + " " + player.SecondName,
Points = scoreStatsQuery
.Where(s => s.PlayerId == player.Id)
.Sum(s => (int?) s.Value) ?? 0,
TeamId = player.TeamId,
TeamName = player.Team.Name,
TeamLogoUrl = player.Team.LogoUrl,
TeamShirtColor = player.Team.ShirtColor,
TeamJerseyStyle = player.Team.JerseyStyle,
TeamShirtSecondaryColor = player.Team.ShirtSecondaryColor,
TeamShirtTertiaryColor = player.Team.ShirtTertiaryColor,
// The dorsal is season-scoped through PlayerTeamRegistration rather than a property of Player itself, so a tournament-scoped ranking uses that season's registration while a season or all-time aggregate spanning several registrations falls back to the player's most recent registration with their current team, since there is no single dorsal to show.
JerseyNumber = filter.TournamentId.HasValue
? _context.Set<PlayerTeamRegistration>()
.Where(r => r.PlayerId == player.Id && r.TournamentId == filter.TournamentId.Value)
.Select(r => r.JerseyNumber)
.FirstOrDefault()
: _context.Set<PlayerTeamRegistration>()
.Where(r => r.PlayerId == player.Id && r.TeamId == player.TeamId)
.OrderByDescending(r => r.DateCreated)
.Select(r => r.JerseyNumber)
.FirstOrDefault(),
});
int totalCount = await query.CountAsync();
List<ScorerByPlayerResponse> items = await query
.OrderByDescending(x => x.Points)
.Skip((filter.PageNumber - 1) * filter.PageSize)
.Take(filter.PageSize)
.ToListAsync();
return (items, totalCount);
}
}
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for Season entities, inheriting generic CRUD from GenericRepository and implementing ISeasonRepository.
/// </summary>
/// <param name="context">The database context used for data access operations.</param>
public class SeasonRepository(ApplicationDBContext context)
: GenericRepository<Season>(context), ISeasonRepository
{
}
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Stage entities, inheriting generic CRUD from GenericRepository and implementing IStageRepository.
/// </summary>
public class StageRepository(ApplicationDBContext context)
: GenericRepository<Stage>(context), IStageRepository
{
}
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing StageTeamMatch entities, inheriting generic CRUD from GenericRepository and implementing IStageTeamMatchRepository.
/// </summary>
public class StageTeamMatchRepository(ApplicationDBContext context)
: GenericRepository<StageTeamMatch>(context), IStageTeamMatchRepository
{ }
@@ -0,0 +1,200 @@
using Application.DTOs.Statistics.Response;
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Domain.Enums;
using Infrastructure.Persistance;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Infrastructure.Repositories;
/// <summary>
/// Historical-statistics aggregations that group each person's rows across seasons by their stable PlayerId, since a person is one Player row reused across seasons rather than duplicated per season.
/// </summary>
public class StatisticsRepository(ApplicationDBContext context) : IStatisticsRepository
{
private readonly ApplicationDBContext _context = context;
/// <inheritdoc/>
public async Task<PlayerStatisticCardResponse?> GetPlayerCardAsync(Guid playerId)
{
Player? player = await _context.Set<Player>()
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == playerId);
if (player is null)
{
return null;
}
List<PointStatRow> rows = await _context.Set<PlayerStatistic>()
.Where(s => s.PlayerId == playerId && s.Type == StatisticType.Points)
.Select(s => new PointStatRow(s.MatchId, s.Value, s.Match!.Stage.Division.TournamentId))
.ToListAsync();
Dictionary<Guid, int> seasonByTournament = await BuildSeasonByTournamentAsync(rows.Select(r => r.TournamentId));
List<SeasonStatLineResponse> seasons = [.. rows
.GroupBy(r => seasonByTournament.TryGetValue(r.TournamentId, out int year) ? year : (int?)null)
.Where(g => g.Key.HasValue)
.Select(g => BuildSeasonLine(g.Key!.Value, g))
.OrderByDescending(s => s.Season)];
int totalPoints = rows.Sum(r => r.Value);
int gamesPlayed = rows.Select(r => r.MatchId).Distinct().Count();
return new PlayerStatisticCardResponse
{
PlayerId = player.Id,
FullName = player.FullName,
TotalPoints = totalPoints,
GamesPlayed = gamesPlayed,
AveragePoints = Average(totalPoints, gamesPlayed),
Seasons = seasons,
};
}
/// <inheritdoc/>
public async Task<PlayerHistoryResponse?> GetPlayerHistoryAsync(Guid playerId)
{
Player? player = await _context.Set<Player>()
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == playerId);
if (player is null)
{
return null;
}
// Season and team come from the season-scoped registrations, the source of truth for which team and which season, untouched by a team being reassigned to a later tournament.
List<RegistrationRow> registrations = await _context.Set<PlayerTeamRegistration>()
.Where(r => r.PlayerId == playerId)
.Select(r => new RegistrationRow(
r.TournamentId,
r.Tournament!.Name,
r.Tournament!.StartDate,
r.TeamId,
r.Team!.Name))
.ToListAsync();
List<PointStatRow> statRows = await _context.Set<PlayerStatistic>()
.Where(s => s.PlayerId == playerId && s.Type == StatisticType.Points)
.Select(s => new PointStatRow(s.MatchId, s.Value, s.Match!.Stage.Division.TournamentId))
.ToListAsync();
Dictionary<Guid, (int Points, int Games)> statsByTournament = statRows
.GroupBy(r => r.TournamentId)
.ToDictionary(
g => g.Key,
g => (Points: g.Sum(x => x.Value), Games: g.Select(x => x.MatchId).Distinct().Count()));
List<SanctionRow> sanctionRows = await _context.Set<PlayerSanction>()
.Where(ps => ps.PlayerId == playerId)
.Select(ps => new SanctionRow(
ps.Id,
ps.Description,
ps.Duration,
ps.IssuedDate,
ps.MatchId,
ps.Match.Stage.Division.TournamentId))
.ToListAsync();
Dictionary<Guid, List<PlayerHistorySanctionResponse>> sanctionsByTournament = sanctionRows
.GroupBy(s => s.TournamentId)
.ToDictionary(
g => g.Key,
g => g.Select(s => new PlayerHistorySanctionResponse
{
SanctionId = s.Id,
Description = s.Description,
Duration = s.Duration,
IssuedDate = s.IssuedDate,
MatchId = s.MatchId,
}).ToList());
List<PlayerHistorySeasonResponse> seasons = [.. registrations
.Select(r =>
{
(int points, int games) = statsByTournament.TryGetValue(r.TournamentId, out (int Points, int Games) stat)
? stat
: (0, 0);
IEnumerable<PlayerHistorySanctionResponse> sanctions =
sanctionsByTournament.TryGetValue(r.TournamentId, out List<PlayerHistorySanctionResponse>? list)
? list
: [];
return new PlayerHistorySeasonResponse
{
Season = r.StartDate.Year,
TournamentId = r.TournamentId,
TournamentName = r.TournamentName,
TeamId = r.TeamId,
TeamName = r.TeamName,
TotalPoints = points,
GamesPlayed = games,
Sanctions = sanctions,
};
})
.OrderByDescending(s => s.Season)
.ThenBy(s => s.TournamentName)];
return new PlayerHistoryResponse
{
PlayerId = player.Id,
FullName = player.FullName,
Seasons = seasons,
};
}
/// <summary>
/// Resolves each tournament id to its season, the year of its StartDate, in one query computed in memory so the season definition stays provider-independent.
/// </summary>
private async Task<Dictionary<Guid, int>> BuildSeasonByTournamentAsync(IEnumerable<Guid> tournamentIds)
{
List<Guid> ids = [.. tournamentIds.Distinct()];
if (ids.Count == 0)
{
return [];
}
List<TournamentSeasonRow> rows = await _context.Set<Tournament>()
.Where(t => ids.Contains(t.Id))
.Select(t => new TournamentSeasonRow(t.Id, t.StartDate))
.ToListAsync();
return rows.ToDictionary(r => r.Id, r => r.StartDate.Year);
}
private static SeasonStatLineResponse BuildSeasonLine(int season, IEnumerable<PointStatRow> rows)
{
int totalPoints = rows.Sum(r => r.Value);
int gamesPlayed = rows.Select(r => r.MatchId).Distinct().Count();
return new SeasonStatLineResponse
{
Season = season,
TotalPoints = totalPoints,
GamesPlayed = gamesPlayed,
AveragePoints = Average(totalPoints, gamesPlayed),
};
}
private static double Average(int totalPoints, int gamesPlayed) =>
gamesPlayed == 0 ? 0 : Math.Round((double)totalPoints / gamesPlayed, 2);
private sealed record PointStatRow(Guid MatchId, int Value, Guid TournamentId);
private sealed record RegistrationRow(Guid TournamentId, string TournamentName, DateTime StartDate, Guid TeamId, string TeamName);
private sealed record SanctionRow(Guid Id, string Description, int Duration, DateTime IssuedDate, Guid MatchId, Guid TournamentId);
private sealed record TournamentSeasonRow(Guid Id, DateTime StartDate);
}
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for TeamPointDeduction entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class TeamPointDeductionRepository(ApplicationDBContext context)
: GenericRepository<TeamPointDeduction>(context), ITeamPointDeductionRepository
{
}
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Team entities, inheriting generic CRUD from GenericRepository and implementing ITeamRepository.
/// </summary>
public class TeamRepository(ApplicationDBContext context)
: GenericRepository<Team>(context), ITeamRepository
{ }
@@ -0,0 +1,15 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for TeamStaff entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class TeamStaffRepository(ApplicationDBContext context)
: GenericRepository<TeamStaff>(context), ITeamStaffRepository
{
}
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing TeamTournamentRegistration entities, inheriting generic CRUD from GenericRepository.
/// </summary>
public class TeamTournamentRegistrationRepository(ApplicationDBContext context)
: GenericRepository<TeamTournamentRegistration>(context), ITeamTournamentRegistrationRepository
{ }
@@ -0,0 +1,14 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for managing Tournament entities, inheriting generic CRUD from GenericRepository and implementing ITournamentRepository.
/// </summary>
public class TournamentRepository(ApplicationDBContext context)
: GenericRepository<Tournament>(context), ITournamentRepository
{ }
@@ -0,0 +1,147 @@
using Application.Interfaces.Repositories;
using Infrastructure.Persistance;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Threading.Tasks;
namespace Infrastructure.Repositories;
/// <summary>
/// Implements IUnitOfWork, providing a centralized access point for all repository instances and managing transactional operations within the application's data layer.
/// </summary>
public class UnitOfWork(
ApplicationDBContext context,
IPlayerRepository playerRepository,
IClubRepository clubRepository,
ITeamRepository teamRepository,
IDivisionRepository divisionRepository,
IMatchRepository matchRepository,
IMatchSeriesRepository matchSeriesRepository,
ITournamentRepository tournamentRepository,
IBlogPostRepository blogPostRepository,
IPlayerSanctionRepository playerSanctionRepository,
IPlayerStatisticRepository playerStatisticRepository,
IVenueRepository venueRepository,
IStageRepository stageRepository,
IStageTeamMatchRepository stageTeamMatchRepository,
IPlayerTeamRegistrationRepository playerTeamRegistrationRepository,
ITeamTournamentRegistrationRepository teamTournamentRegistrationRepository,
IDivisionTeamRegistrationRepository divisionTeamRegistrationRepository
) : IUnitOfWork
{
/// <summary>
/// Gets the repository for blog post entities.
/// </summary>
public IBlogPostRepository BlogPostRepository { get; } = blogPostRepository;
/// <summary>
/// Gets the repository for club entities, whose identity stays stable across seasons.
/// </summary>
public IClubRepository ClubRepository { get; } = clubRepository;
/// <summary>
/// Gets the repository for team entities.
/// </summary>
public ITeamRepository TeamRepository { get; } = teamRepository;
/// <summary>
/// Gets the repository for venue entities.
/// </summary>
public IVenueRepository VenueRepository { get; } = venueRepository;
/// <summary>
/// Gets the repository for tournament entities.
/// </summary>
public ITournamentRepository TournamentRepository { get; } = tournamentRepository;
/// <summary>
/// Gets the repository for stage entities.
/// </summary>
public IStageRepository StageRepository { get; } = stageRepository;
/// <summary>
/// Gets the repository for player statistic entities.
/// </summary>
public IPlayerStatisticRepository PlayerStatisticRepository { get; } = playerStatisticRepository;
/// <summary>
/// Gets the repository for player sanction entities.
/// </summary>
public IPlayerSanctionRepository PlayerSanctionRepository { get; } = playerSanctionRepository;
/// <summary>
/// Gets the repository for player entities.
/// </summary>
public IPlayerRepository PlayerRepository { get; } = playerRepository;
/// <summary>
/// Gets the repository for match entities.
/// </summary>
public IMatchRepository MatchRepository { get; } = matchRepository;
/// <summary>
/// Gets the repository for match series entities.
/// </summary>
public IMatchSeriesRepository MatchSeriesRepository { get; } = matchSeriesRepository;
/// <summary>
/// Gets the repository for division entities.
/// </summary>
public IDivisionRepository DivisionRepository { get; } = divisionRepository;
/// <summary>
/// Gets the repository for stage team match entities.
/// </summary>
public IStageTeamMatchRepository StageTeamMatchRepository { get; } = stageTeamMatchRepository;
/// <summary>
/// Gets the repository for player-team registration entities.
/// </summary>
public IPlayerTeamRegistrationRepository PlayerTeamRegistrationRepository { get; } = playerTeamRegistrationRepository;
/// <summary>
/// Gets the repository for team-tournament registration entities.
/// </summary>
public ITeamTournamentRegistrationRepository TeamTournamentRegistrationRepository { get; } = teamTournamentRegistrationRepository;
/// <summary>
/// Gets the repository for division-team registration entities.
/// </summary>
public IDivisionTeamRegistrationRepository DivisionTeamRegistrationRepository { get; } = divisionTeamRegistrationRepository;
/// <summary>
/// Commits all tracked changes to the database asynchronously.
/// </summary>
/// <returns>The number of state entries written to the database.</returns>
public async Task<int> SaveChangesAsync()
{
return await context.SaveChangesAsync();
}
/// <inheritdoc/>
public async Task ExecuteInTransactionAsync(Func<Task> operation)
{
IExecutionStrategy strategy = context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
await using IDbContextTransaction transaction = await context.Database.BeginTransactionAsync();
try
{
await operation();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
});
}
}
@@ -0,0 +1,16 @@
using Application.Interfaces.Repositories;
using Domain.Entities.Models;
using Infrastructure.Persistance;
namespace Infrastructure.Repositories;
/// <summary>
/// Repository implementation for Venue entities, inheriting generic CRUD from GenericRepository and implementing IVenueRepository.
/// </summary>
/// <param name="context">The database context used for data access operations.</param>
public class VenueRepository(ApplicationDBContext context)
: GenericRepository<Venue>(context), IVenueRepository
{
}