Skip to content

Commit

Permalink
Merge pull request #49 from theImmortalCoders/dev
Browse files Browse the repository at this point in the history
Deploy
  • Loading branch information
marcinbator authored Nov 12, 2024
2 parents c6bca19 + dbae4a7 commit a04ec1e
Show file tree
Hide file tree
Showing 25 changed files with 316 additions and 46 deletions.
1 change: 1 addition & 0 deletions rag-2-backend/Config/ServiceRegistrationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ private static void ConfigServices(IServiceCollection services)
services.AddScoped<RefreshTokenDao>();
services.AddScoped<GameDao>();
services.AddScoped<GameRecordDao>();
services.AddScoped<StatsUtil>();
}

private static void ConfigSwagger(IServiceCollection services)
Expand Down
3 changes: 2 additions & 1 deletion rag-2-backend/Infrastructure/Common/Mapper/GameMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ public static GameResponse Map(Game game)
return new GameResponse
{
Id = game.Id,
Name = game.Name
Name = game.Name,
Description = game.Description
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@ public class GameRecordValue
public object? State { get; init; }
public List<Player>? Players { get; init; }
public string? Timestamp { get; init; }
public string? OutputSpec { get; init; }
}
5 changes: 5 additions & 0 deletions rag-2-backend/Infrastructure/Dao/GameDao.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public virtual Game GetGameByNameOrThrow(string gameName)
return dbContext.Games.SingleOrDefault(g => Equals(g.Name.ToLower(), gameName.ToLower()))
?? throw new NotFoundException("Game not found");
}

public virtual List<Game> GetAllGames()
{
return dbContext.Games.ToList();
}
}
13 changes: 13 additions & 0 deletions rag-2-backend/Infrastructure/Dao/GameRecordDao.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ public virtual GameRecord GetRecordedGameById(int recordedGameId)
?? throw new NotFoundException("Game record not found");
}

public virtual double CountTotalStorageMb()
{
return dbContext.GameRecords
.Select(r => r.SizeMb)
.ToList()
.Sum();
}

public virtual int CountAllGameRecords()
{
return dbContext.GameRecords.Count();
}

public virtual void PerformGameRecordTransaction(Game game, GameRecord gameRecord,
User user)
{
Expand Down
5 changes: 5 additions & 0 deletions rag-2-backend/Infrastructure/Dao/UserDao.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,9 @@ public virtual User GetUserByEmailOrThrow(string email)
return context.Users.SingleOrDefault(u => u.Email == email) ??
throw new NotFoundException("User not found");
}

public virtual int CountUsers()
{
return context.Users.Count();
}
}
1 change: 1 addition & 0 deletions rag-2-backend/Infrastructure/Database/Entity/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ public class Game
{
[Key] public int Id { get; init; }
[MaxLength(100)] public required string Name { get; set; }
[MaxLength(1000)] public string? Description { get; set; }
}
2 changes: 2 additions & 0 deletions rag-2-backend/Infrastructure/Database/Entity/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using HttpExceptions.Exceptions;
using Microsoft.EntityFrameworkCore;
using rag_2_backend.Infrastructure.Common.Model;

#endregion

namespace rag_2_backend.Infrastructure.Database.Entity;

[Table("user_table")]
[Index(nameof(Email), IsUnique = true)]
public class User
{
public User() //for ef
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@

using Microsoft.EntityFrameworkCore;
using rag_2_backend.Infrastructure.Database;
using rag_2_backend.Infrastructure.Util;

#endregion

namespace rag_2_backend.Infrastructure.Module.Background;

public class BackgroundServiceImpl(IServiceProvider serviceProvider) : BackgroundService
public class BackgroundServiceImpl(
IServiceProvider serviceProvider
) : BackgroundService
{
private DatabaseContext _dbContext = null!;
private StatsUtil _statsUtil = null!;

protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
using var scope = serviceProvider.CreateScope();
_dbContext = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
_statsUtil = scope.ServiceProvider.GetRequiredService<StatsUtil>();

while (!cancellationToken.IsCancellationRequested)
{
DeleteUnusedAccountTokens();
DeleteUnusedRefreshTokens();
DeleteUnusedPasswordResetTokens();
UpdateCachedStats();

await Task.Delay(TimeSpan.FromDays(1), cancellationToken);
await Task.Delay(TimeSpan.FromHours(3), cancellationToken);
}
}

Expand Down Expand Up @@ -64,4 +70,14 @@ private void DeleteUnusedPasswordResetTokens()

Console.WriteLine("Deleted " + unusedTokens.Count + " expired password reset tokens");
}

private async void UpdateCachedStats()
{
var games = await _dbContext.Games.ToListAsync();
foreach (var game in games) _statsUtil.UpdateCachedGameStats(game);

_statsUtil.UpdateCachedStats();

Console.WriteLine("Stats updated.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ namespace rag_2_backend.Infrastructure.Module.Game.Dto;
public class GameRequest
{
public required string Name { get; init; }
public string? Description { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ public class GameResponse
{
public int Id { get; init; }
public required string Name { get; init; }
public string? Description { get; set; }
}
4 changes: 3 additions & 1 deletion rag-2-backend/Infrastructure/Module/Game/GameService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ public void AddGame(GameRequest request)

var game = new Database.Entity.Game
{
Name = request.Name
Name = request.Name,
Description = request.Description
};

context.Games.Add(game);
Expand All @@ -42,6 +43,7 @@ public void EditGame(GameRequest request, int id)
throw new BadRequestException("Game with this name already exists");

game.Name = request.Name;
game.Description = request.Description;

context.Games.Update(game);
context.SaveChanges();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ namespace rag_2_backend.Infrastructure.Module.GameRecord.Dto;
public class GameRecordRequest
{
public required string GameName { get; init; }
public required string OutputSpec { get; init; }
public required List<GameRecordValue> Values { get; init; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void AddGameRecord(GameRecordRequest recordRequest, string email)
Values = recordRequest.Values,
User = user,
Players = recordRequest.Values[0].Players,
OutputSpec = recordRequest.Values[0].OutputSpec ?? "",
OutputSpec = recordRequest.OutputSpec,
EndState = recordRequest.Values[^1].State?.ToString(),
SizeMb = JsonSerializer.Serialize(recordRequest.Values).Length / (1024.0 * 1024.0)
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace rag_2_backend.Infrastructure.Module.Stats.Dto;

public class OverallStatsResponse
{
public required int PlayersAmount { get; init; }
public required double TotalMemoryMb { get; init; }
public required int GamesAmount { get; init; }
public required int GameRecordsAmount { get; init; }
public DateTime StatsUpdatedDate { get; init; }
}
7 changes: 7 additions & 0 deletions rag-2-backend/Infrastructure/Module/Stats/StatsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,11 @@ public GameStatsResponse GetStatsForGame([Required] [FromQuery] int gameId)
{
return statsService.GetStatsForGame(gameId);
}

/// <summary>Get overall stats</summary>
[HttpGet("all")]
public OverallStatsResponse GetOverallStats()
{
return statsService.GetOverallStats();
}
}
52 changes: 17 additions & 35 deletions rag-2-backend/Infrastructure/Module/Stats/StatsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Newtonsoft.Json;
using rag_2_backend.Infrastructure.Dao;
using rag_2_backend.Infrastructure.Module.Stats.Dto;
using rag_2_backend.Infrastructure.Util;
using StackExchange.Redis;
using IDatabase = StackExchange.Redis.IDatabase;
using Role = rag_2_backend.Infrastructure.Common.Model.Role;
Expand All @@ -13,13 +14,14 @@
namespace rag_2_backend.Infrastructure.Module.Stats;

public class StatsService(
IConfiguration configuration,
IConnectionMultiplexer redisConnection,
UserDao userDao,
GameDao gameDao,
GameRecordDao gameRecordDao
GameRecordDao gameRecordDao,
StatsUtil statsUtil
)
{
private const string RedisCacheKeyPrefix = "GameStats:";
private readonly IDatabase _redisDatabase = redisConnection.GetDatabase();

public UserStatsResponse GetStatsForUser(string email, int userId)
Expand Down Expand Up @@ -49,43 +51,23 @@ public GameStatsResponse GetStatsForGame(int gameId)
{
var game = gameDao.GetGameByIdOrThrow(gameId);

var cacheKey = $"{RedisCacheKeyPrefix}{game.Id}";
var cacheKey = $"{configuration.GetValue<string>("Redis:Stats:Prefix")}{game.Id}";
var cachedStatsJson = _redisDatabase.StringGet(cacheKey);

if (!string.IsNullOrEmpty(cachedStatsJson))
{
var cachedStats = JsonConvert.DeserializeObject<GameStatsResponse>(cachedStatsJson!);
if (cachedStats.StatsUpdatedDate.AddDays(1) >= DateTime.Now)
return cachedStats;
}

return UpdateCachedStats(gameId, game);
return !string.IsNullOrEmpty(cachedStatsJson)
? JsonConvert.DeserializeObject<GameStatsResponse>(cachedStatsJson!)
: statsUtil.UpdateCachedGameStats(game);
}

//

private GameStatsResponse UpdateCachedStats(int gameId, Database.Entity.Game game)
public OverallStatsResponse GetOverallStats()
{
var records = gameRecordDao.GetGameRecordsByGameWithUser(gameId);

var gameStatsResponse = new GameStatsResponse
{
FirstPlayed = records.Count > 0 ? records[0].Started : null,
LastPlayed = records.Count > 0 ? records.Last().Ended : null,
Plays = records.Count,
TotalStorageMb = gameRecordDao.GetGameRecordsByGameWithUser(gameId)
.Select(r => r.SizeMb)
.ToList()
.Sum(),
TotalPlayers = records.Select(r => r.User.Id).Distinct().Count(),
StatsUpdatedDate = DateTime.Now
};

var cacheKey = $"{RedisCacheKeyPrefix}{game.Id}";
var serializedStats = JsonConvert.SerializeObject(gameStatsResponse);

_redisDatabase.StringSet(cacheKey, serializedStats, TimeSpan.FromDays(1));

return gameStatsResponse;
var cachedStatsJson = _redisDatabase.StringGet(
configuration.GetValue<string>("Redis:Stats:Prefix") +
configuration.GetValue<string>("Redis:Stats:OverallStatsKey")
);

return !string.IsNullOrEmpty(cachedStatsJson)
? JsonConvert.DeserializeObject<OverallStatsResponse>(cachedStatsJson!)
: statsUtil.UpdateCachedStats();
}
}
67 changes: 67 additions & 0 deletions rag-2-backend/Infrastructure/Util/StatsUtil.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#region

using Newtonsoft.Json;
using rag_2_backend.Infrastructure.Dao;
using rag_2_backend.Infrastructure.Database.Entity;
using rag_2_backend.Infrastructure.Module.Stats.Dto;
using StackExchange.Redis;

#endregion

namespace rag_2_backend.Infrastructure.Util;

public class StatsUtil(
IConfiguration configuration,
IConnectionMultiplexer redisConnection,
UserDao userDao,
GameDao gameDao,
GameRecordDao gameRecordDao
)
{
private readonly IDatabase _redisDatabase = redisConnection.GetDatabase();

public GameStatsResponse UpdateCachedGameStats(Game game)
{
var records = gameRecordDao.GetGameRecordsByGameWithUser(game.Id);

var gameStatsResponse = new GameStatsResponse
{
FirstPlayed = records.Count > 0 ? records[0].Started : null,
LastPlayed = records.Count > 0 ? records.Last().Ended : null,
Plays = records.Count,
TotalStorageMb = gameRecordDao.GetGameRecordsByGameWithUser(game.Id)
.Select(r => r.SizeMb)
.ToList()
.Sum(),
TotalPlayers = records.Select(r => r.User.Id).Distinct().Count(),
StatsUpdatedDate = DateTime.Now
};

var cacheKey = $"{configuration.GetValue<string>("Redis:Stats:Prefix")}{game.Id}";
var serializedStats = JsonConvert.SerializeObject(gameStatsResponse);

_redisDatabase.StringSet(cacheKey, serializedStats, TimeSpan.FromDays(1));

return gameStatsResponse;
}

public OverallStatsResponse UpdateCachedStats()
{
var overallStatsResponse = new OverallStatsResponse
{
PlayersAmount = userDao.CountUsers(),
TotalMemoryMb = gameRecordDao.CountTotalStorageMb(),
GamesAmount = gameDao.GetAllGames().Count,
GameRecordsAmount = gameRecordDao.CountAllGameRecords(),
StatsUpdatedDate = DateTime.Now
};

var serializedStats = JsonConvert.SerializeObject(overallStatsResponse);
_redisDatabase.StringSet(
configuration.GetValue<string>("Redis:Stats:Prefix") +
configuration.GetValue<string>("Redis:Stats:OverallStatsKey"),
serializedStats, TimeSpan.FromDays(1));

return overallStatsResponse;
}
}
3 changes: 3 additions & 0 deletions rag-2-backend/Migrations/20241107192454_Base.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit a04ec1e

Please sign in to comment.