177 lines
5.3 KiB
C#
177 lines
5.3 KiB
C#
using Koogle.Application.DTOs;
|
|
using Koogle.Application.Interfaces;
|
|
using Koogle.Domain.Entities;
|
|
using Koogle.Domain.Enums;
|
|
using KoogleApp.Data;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Koogle.Application.Services;
|
|
|
|
/// <summary>
|
|
/// Service for persisting and recovering game state.
|
|
/// </summary>
|
|
public class GamePersistenceService : IGamePersistenceService
|
|
{
|
|
private readonly IDbContextFactory<AppDbContext> _contextFactory;
|
|
private readonly ILogger<GamePersistenceService> _logger;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the GamePersistenceService class.
|
|
/// </summary>
|
|
public GamePersistenceService(
|
|
IDbContextFactory<AppDbContext> contextFactory,
|
|
ILogger<GamePersistenceService> logger)
|
|
{
|
|
_contextFactory = contextFactory;
|
|
_logger = logger;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<Guid> CreateGameAsync(CreateGameDto dto)
|
|
{
|
|
await using var context = await _contextFactory.CreateDbContextAsync();
|
|
|
|
var game = new Game
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
DayId = dto.DayId,
|
|
ClubId = dto.ClubId,
|
|
GameType = dto.GameType,
|
|
GameData = dto.InitialGameStateJson,
|
|
Status = GameStatus.Active,
|
|
StartedAt = DateTime.UtcNow,
|
|
CreatedAt = DateTime.UtcNow
|
|
};
|
|
|
|
// Add game participants
|
|
foreach (var playerId in dto.PlayerIds)
|
|
{
|
|
game.GamePersons.Add(new GamePerson
|
|
{
|
|
GameId = game.Id,
|
|
PersonId = playerId,
|
|
ClubId = dto.ClubId
|
|
});
|
|
}
|
|
|
|
context.Games.Add(game);
|
|
await context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation(
|
|
"Game created: {GameId}, Type: {GameType}, Day: {DayId}, Players: {PlayerCount}",
|
|
game.Id, dto.GameType, dto.DayId, dto.PlayerIds.Length);
|
|
|
|
return game.Id;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<bool> SaveGameStateAsync(Guid gameId, string gameStateJson)
|
|
{
|
|
await using var context = await _contextFactory.CreateDbContextAsync();
|
|
|
|
var game = await context.Games.FindAsync(gameId);
|
|
if (game == null)
|
|
{
|
|
_logger.LogWarning("Game not found: {GameId}", gameId);
|
|
return false;
|
|
}
|
|
|
|
game.GameData = gameStateJson;
|
|
game.ModifiedAt = DateTime.UtcNow;
|
|
|
|
try
|
|
{
|
|
await context.SaveChangesAsync();
|
|
_logger.LogDebug("Game state saved: {GameId}", gameId);
|
|
return true;
|
|
}
|
|
catch (DbUpdateConcurrencyException ex)
|
|
{
|
|
_logger.LogWarning(ex, "Concurrency conflict saving game: {GameId}", gameId);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<ActiveGameDto?> LoadActiveGameAsync(Guid dayId)
|
|
{
|
|
await using var context = await _contextFactory.CreateDbContextAsync();
|
|
|
|
var game = await context.Games
|
|
.Include(g => g.GamePersons)
|
|
.Where(g => g.DayId == dayId && g.Status == GameStatus.Active && !g.IsDeleted)
|
|
.FirstOrDefaultAsync();
|
|
|
|
if (game == null)
|
|
{
|
|
_logger.LogDebug("No active game found for day: {DayId}", dayId);
|
|
return null;
|
|
}
|
|
|
|
_logger.LogInformation(
|
|
"Active game loaded: {GameId}, Type: {GameType}",
|
|
game.Id, game.GameType);
|
|
|
|
return new ActiveGameDto
|
|
{
|
|
Id = game.Id,
|
|
DayId = game.DayId,
|
|
GameType = game.GameType,
|
|
StartedAt = game.StartedAt,
|
|
PlayerIds = game.GamePersons.Select(gp => gp.PersonId).ToArray(),
|
|
GameStateJson = game.GameData
|
|
};
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task EndGameAsync(Guid gameId, GameStatus status)
|
|
{
|
|
if (status == GameStatus.Active)
|
|
{
|
|
throw new ArgumentException("Cannot end game with Active status", nameof(status));
|
|
}
|
|
|
|
await using var context = await _contextFactory.CreateDbContextAsync();
|
|
|
|
var game = await context.Games.FindAsync(gameId);
|
|
if (game == null)
|
|
{
|
|
_logger.LogWarning("Game not found for ending: {GameId}", gameId);
|
|
return;
|
|
}
|
|
|
|
game.Status = status;
|
|
game.CompletedAt = DateTime.UtcNow;
|
|
game.ModifiedAt = DateTime.UtcNow;
|
|
|
|
await context.SaveChangesAsync();
|
|
|
|
_logger.LogInformation(
|
|
"Game ended: {GameId}, Status: {Status}",
|
|
gameId, status);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public async Task<IReadOnlyList<GameSummaryDto>> GetCompletedGamesAsync(Guid dayId)
|
|
{
|
|
await using var context = await _contextFactory.CreateDbContextAsync();
|
|
|
|
var games = await context.Games
|
|
.Include(g => g.GamePersons)
|
|
.Where(g => g.DayId == dayId && g.Status != GameStatus.Active && !g.IsDeleted)
|
|
.OrderByDescending(g => g.CompletedAt)
|
|
.ToListAsync();
|
|
|
|
return games.Select(g => new GameSummaryDto
|
|
{
|
|
Id = g.Id,
|
|
GameTypeName = g.GameType,
|
|
Status = g.Status,
|
|
StartedAt = g.StartedAt,
|
|
CompletedAt = g.CompletedAt,
|
|
ParticipantCount = g.GamePersons.Count
|
|
}).ToList();
|
|
}
|
|
}
|