Skip to content

Commit

Permalink
Merge pull request #27 from theImmortalCoders/dev
Browse files Browse the repository at this point in the history
Fixes - p1
  • Loading branch information
marcinbator authored Oct 15, 2024
2 parents bf13ea5 + d9055cb commit 0365a55
Show file tree
Hide file tree
Showing 10 changed files with 266 additions and 21 deletions.
3 changes: 2 additions & 1 deletion rag-2-backend/Config/ServiceRegistrationExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ private static void ConfigServices(IServiceCollection services)
services.AddScoped<EmailService>();
services.AddScoped<JwtSecurityTokenHandler>();
services.AddScoped<AdministrationService>();
services.AddScoped<StatsService>();
services.AddScoped<UserUtil>();

services.AddSingleton<StatsService>();
}

private static void ConfigSwagger(IServiceCollection services)
Expand Down
8 changes: 6 additions & 2 deletions rag-2-backend/Controllers/GameRecordController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,22 @@ namespace rag_2_backend.controllers;
[Route("api/[controller]")]
public class GameRecordController(GameRecordService gameRecordService) : ControllerBase
{
/// <summary>Get all recorded games for user by game ID (Auth)</summary>
/// <summary>Get all recorded games for user by game ID and user (Auth)</summary>
/// <response code="404">User or game not found</response>
[HttpGet]
[Authorize]
public List<RecordedGameResponse> GetRecordsByGame([Required] int gameId)
{
return gameRecordService.GetRecordsByGame(gameId);
var email = UserUtil.GetPrincipalEmail(User);

return gameRecordService.GetRecordsByGameAndUser(gameId, email);
}

/// <summary>Download JSON file from specific game, admin and teacher can download everyone's data (Auth)</summary>
/// <response code="404">User or game record not found</response>
/// <response code="403">Permission denied</response>
[HttpGet("{recordedGameId:int}")]
[Authorize]
public FileContentResult DownloadRecordData([Required] int recordedGameId)
{
var email = UserUtil.GetPrincipalEmail(User);
Expand Down
1 change: 1 addition & 0 deletions rag-2-backend/DTO/Stats/GameStatsResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ public class GameStatsResponse
public double TotalStorageMb { get; set; }
public DateTime FirstPlayed { get; init; }
public DateTime LastPlayed { get; init; }
public DateTime StatsUpdatedDate { get; init; }
}
4 changes: 2 additions & 2 deletions rag-2-backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ EXPOSE 8081
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build

WORKDIR /src
COPY ["rag-2-backend/rag-2-backend/rag-2-backend.csproj", "./"]
COPY ["rag-2-backend/rag-2-backend.csproj", "./"]
RUN dotnet restore "rag-2-backend.csproj"
COPY ["rag-2-backend/rag-2-backend/", "."]
COPY ["rag-2-backend/", "."]
WORKDIR "/src/"
RUN dotnet build "rag-2-backend.csproj" -c --no-launch-profile -o /app/build

Expand Down
142 changes: 142 additions & 0 deletions rag-2-backend/[email protected]

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions rag-2-backend/Services/GameRecordService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ namespace rag_2_backend.Services;

public class GameRecordService(DatabaseContext context, IConfiguration configuration, UserUtil userUtil)
{
public List<RecordedGameResponse> GetRecordsByGame(int gameId)
public List<RecordedGameResponse> GetRecordsByGameAndUser(int gameId, string email)
{
return context.RecordedGames
.Include(r => r.Game)
.Include(r => r.User)
.Where(r => r.Game.Id == gameId)
.Where(r => r.Game.Id == gameId && r.User.Email == email)
.ToList()
.Select(RecordedGameMapper.Map)
.ToList();
Expand Down
46 changes: 34 additions & 12 deletions rag-2-backend/Services/StatsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@
using rag_2_backend.Config;
using rag_2_backend.DTO.Stats;
using rag_2_backend.Models;
using rag_2_backend.Utils;
using rag_2_backend.models.entity;

#endregion

namespace rag_2_backend.Services;

public class StatsService(DatabaseContext context, UserUtil userUtil)
public class StatsService(IServiceProvider serviceProvider)
{
private readonly DatabaseContext _context =
serviceProvider.CreateScope().ServiceProvider.GetRequiredService<DatabaseContext>();

private Dictionary<int, GameStatsResponse> CachedGameStats { get; } = new();

public UserStatsResponse GetStatsForUser(string email, int userId)
{
var user = userUtil.GetUserByEmailOrThrow(email);
var user = _context.Users.FirstOrDefault(u => u.Id == userId)
?? throw new NotFoundException("User not found");

if (user.Email != email && user.Role == Role.Student)
throw new ForbiddenException("Permission denied");

var records = context.RecordedGames
var records = _context.RecordedGames
.OrderBy(r => r.Started)
.Where(r => r.User.Id == userId).Include(recordedGame => recordedGame.Game)
.ToList();
Expand All @@ -37,26 +43,42 @@ public UserStatsResponse GetStatsForUser(string email, int userId)

public GameStatsResponse GetStatsForGame(int gameId)
{
var records = context.RecordedGames
var game = _context.Games.FirstOrDefault(g => g.Id == gameId)
?? throw new NotFoundException("Game not found");

if (CachedGameStats.TryGetValue(game.Id, out var value)
&& value.StatsUpdatedDate.AddDays(1) >= DateTime.Now)
return CachedGameStats[game.Id];

return UpdateCachedStats(gameId, game);
}

//

private GameStatsResponse UpdateCachedStats(int gameId, Game game)
{
var records = _context.RecordedGames
.OrderBy(r => r.Started)
.Where(r => r.Game.Id == gameId).Include(recordedGame => recordedGame.User)
.Where(r => r.Game.Id == gameId)
.Include(recordedGame => recordedGame.User)
.ToList();

return new GameStatsResponse
CachedGameStats[game.Id] = new GameStatsResponse
{
FirstPlayed = records[0].Started,
LastPlayed = records.Last().Ended,
Plays = records.Count,
TotalStorageMb = GetSizeByGame(gameId, 0),
TotalPlayers = records.Select(r => r.User.Id).Distinct().Count()
TotalPlayers = records.Select(r => r.User.Id).Distinct().Count(),
StatsUpdatedDate = DateTime.Now
};
}

//
return CachedGameStats[game.Id];
}

private double GetSizeByGame(int gameId, double initialSizeBytes)
{
var results = context.RecordedGames
var results = _context.RecordedGames
.Where(e => e.Game.Id == gameId)
.Select(e => new
{
Expand All @@ -70,7 +92,7 @@ private double GetSizeByGame(int gameId, double initialSizeBytes)

private double GetSizeByUser(int userId, double initialSizeBytes)
{
var results = context.RecordedGames
var results = _context.RecordedGames
.Where(e => e.User.Id == userId)
.Select(e => new
{
Expand Down
2 changes: 1 addition & 1 deletion rag-2-backend/Test/GameRecordServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ public GameRecordServiceTest()
[Fact]
public void GetRecordsByGameTest()
{
var actualRecords = _gameRecordService.GetRecordsByGame(1);
var actualRecords = _gameRecordService.GetRecordsByGameAndUser(1, "[email protected]");
List<RecordedGameResponse> expectedRecords =
[
new()
Expand Down
28 changes: 27 additions & 1 deletion rag-2-backend/Test/StatsServiceTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ public class StatsServiceTests
public StatsServiceTests()
{
_mockUserUtil = new Mock<UserUtil>(_contextMock.Object);
_statsService = new StatsService(_contextMock.Object, _mockUserUtil.Object);

var serviceProvider = MockServiceProvider();

_statsService = new StatsService(serviceProvider.Object);

_contextMock.Setup(c => c.RecordedGames).Returns(
_recordedGames.AsQueryable().BuildMockDbSet().Object
Expand Down Expand Up @@ -91,4 +94,27 @@ public void ShouldReturnStatsForGame()
Assert.Equal(1, result.Plays);
Assert.Equal(1, result.TotalPlayers);
}

//

private Mock<IServiceProvider> MockServiceProvider()
{
var serviceProvider = new Mock<IServiceProvider>();
serviceProvider
.Setup(x => x.GetService(typeof(DatabaseContext)))
.Returns(_contextMock.Object);

var serviceScope = new Mock<IServiceScope>();
serviceScope.Setup(x => x.ServiceProvider).Returns(serviceProvider.Object);

var serviceScopeFactory = new Mock<IServiceScopeFactory>();
serviceScopeFactory
.Setup(x => x.CreateScope())
.Returns(serviceScope.Object);

serviceProvider
.Setup(x => x.GetService(typeof(IServiceScopeFactory)))
.Returns(serviceScopeFactory.Object);
return serviceProvider;
}
}
49 changes: 49 additions & 0 deletions rag-2-backend/public.uml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<Diagram>
<ID>DATABASE</ID>
<OriginalElement>fa1a1854-887a-48ed-9eec-82dbd016d9ae.SCHEMA:postgres.public</OriginalElement>
<nodes>
<node x="316.5" y="475.5">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.recorded_game</node>
<node x="-137.5" y="544.5">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.password_reset_token</node>
<node x="260.0" y="-124.0">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.game_table</node>
<node x="-3.0" y="344.5">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.account_confirmation_token</node>
<node x="4.5" y="-4.5">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.user_table</node>
<node x="353.5" y="76.0">fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.blacklisted_jwt</node>
</nodes>
<notes />
<edges>
<edge source="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.password_reset_token" target="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.user_table" relationship="REFERENCES">
<point x="0.0" y="-54.5" />
<point x="-16.03621621621619" y="319.0" />
<point x="117.0" y="319.0" />
<point x="0.0" y="123.5" />
</edge>
<edge source="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.recorded_game" target="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.game_table" relationship="REFERENCES">
<point x="60.73189189189188" y="-123.5" />
<point x="498.6956756756757" y="299.0" />
<point x="335.0" y="299.0" />
<point x="0.0" y="43.0" />
</edge>
<edge source="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.recorded_game" target="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.user_table" relationship="REFERENCES">
<point x="-60.73189189189188" y="-123.5" />
<point x="377.2318918918919" y="319.0" />
<point x="117.0" y="319.0" />
<point x="0.0" y="123.5" />
</edge>
<edge source="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.account_confirmation_token" target="fa1a1854-887a-48ed-9eec-82dbd016d9ae.TABLE:postgres.public.user_table" relationship="REFERENCES">
<point x="0.0" y="-54.5" />
<point x="135.0" y="293.5" />
<point x="117.0" y="293.5" />
<point x="0.0" y="123.5" />
</edge>
</edges>
<settings layout="Hierarchic Compact" zoom="1.0668973471741638" showDependencies="false" x="338.21999999999895" y="311.3745945945947" />
<SelectedNodes />
<Categories>
<Category>Columns</Category>
<Category>Comments</Category>
<Category>Key columns</Category>
<Category>Virtual foreign keys</Category>
</Categories>
</Diagram>

0 comments on commit 0365a55

Please sign in to comment.