diff --git a/rag-2-backend/Infrastructure/Common/Model/SortDirection.cs b/rag-2-backend/Infrastructure/Common/Model/SortDirection.cs new file mode 100644 index 0000000..6d6ce59 --- /dev/null +++ b/rag-2-backend/Infrastructure/Common/Model/SortDirection.cs @@ -0,0 +1,7 @@ +namespace rag_2_backend.Infrastructure.Common.Model; + +public enum SortDirection +{ + Asc, + Desc +} \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Dao/CourseDao.cs b/rag-2-backend/Infrastructure/Dao/CourseDao.cs index eaa6c91..d66051d 100644 --- a/rag-2-backend/Infrastructure/Dao/CourseDao.cs +++ b/rag-2-backend/Infrastructure/Dao/CourseDao.cs @@ -1,6 +1,7 @@ #region using HttpExceptions.Exceptions; +using Microsoft.EntityFrameworkCore; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -10,14 +11,9 @@ namespace rag_2_backend.Infrastructure.Dao; public class CourseDao(DatabaseContext dbContext) { - public virtual Course GetCourseByIdOrThrow(int id) + public virtual async Task GetCourseByIdOrThrow(int id) { - return dbContext.Courses.SingleOrDefault(u => u.Id == id) ?? + return await dbContext.Courses.SingleOrDefaultAsync(u => u.Id == id) ?? throw new NotFoundException("Course not found"); } - - public virtual List GetAllCourses() - { - return dbContext.Courses.ToList(); - } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Dao/GameDao.cs b/rag-2-backend/Infrastructure/Dao/GameDao.cs index 5085f42..ae4a6b4 100644 --- a/rag-2-backend/Infrastructure/Dao/GameDao.cs +++ b/rag-2-backend/Infrastructure/Dao/GameDao.cs @@ -1,6 +1,7 @@ #region using HttpExceptions.Exceptions; +using Microsoft.EntityFrameworkCore; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -10,19 +11,20 @@ namespace rag_2_backend.Infrastructure.Dao; public class GameDao(DatabaseContext dbContext) { - public virtual Game GetGameByIdOrThrow(int id) + public virtual async Task GetGameByIdOrThrow(int id) { - return dbContext.Games.SingleOrDefault(g => g.Id == id) ?? throw new NotFoundException("Game not found"); + return await dbContext.Games.SingleOrDefaultAsync(g => g.Id == id) ?? + throw new NotFoundException("Game not found"); } - public virtual Game GetGameByNameOrThrow(string gameName) + public virtual async Task GetGameByNameOrThrow(string gameName) { - return dbContext.Games.SingleOrDefault(g => Equals(g.Name.ToLower(), gameName.ToLower())) + return await dbContext.Games.SingleOrDefaultAsync(g => Equals(g.Name.ToLower(), gameName.ToLower())) ?? throw new NotFoundException("Game not found"); } - public virtual List GetAllGames() + public virtual async Task> GetAllGames() { - return dbContext.Games.ToList(); + return await dbContext.Games.ToListAsync(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Dao/GameRecordDao.cs b/rag-2-backend/Infrastructure/Dao/GameRecordDao.cs index c409175..ad0170e 100644 --- a/rag-2-backend/Infrastructure/Dao/GameRecordDao.cs +++ b/rag-2-backend/Infrastructure/Dao/GameRecordDao.cs @@ -5,9 +5,11 @@ using Microsoft.EntityFrameworkCore; using Npgsql; using rag_2_backend.Infrastructure.Common.Mapper; +using rag_2_backend.Infrastructure.Common.Model; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; using rag_2_backend.Infrastructure.Module.GameRecord.Dto; +using rag_2_backend.Infrastructure.Util; #endregion @@ -15,54 +17,67 @@ namespace rag_2_backend.Infrastructure.Dao; public class GameRecordDao(DatabaseContext dbContext) { - public virtual List GetRecordsByGameAndUser(int gameId, int userId) + public virtual async Task> GetRecordsByGameAndUser( + int gameId, + int userId, + bool? includeEmptyRecords, + DateTime? endDateFrom, + DateTime? endDateTo, + SortDirection sortDirection, + GameRecordSortByFields sortBy + ) { - return dbContext.GameRecords + var query = dbContext.GameRecords .Include(r => r.Game) .Include(r => r.User) .Where(r => r.Game.Id == gameId && r.User.Id == userId) - .ToList() + .AsQueryable(); + + query = FilterGameRecords(includeEmptyRecords, endDateFrom, endDateTo, query); + query = SortGameRecords(sortDirection, sortBy, query); + + return await Task.Run(() => query.AsEnumerable() .Select(GameRecordMapper.Map) - .ToList(); + .ToList()); } - public virtual List GetGameRecordsByUserWithGame(int userId) + public virtual async Task> GetGameRecordsByUserWithGame(int userId) { - return dbContext.GameRecords + return await dbContext.GameRecords .OrderBy(r => r.Started) .Where(r => r.User.Id == userId) .Include(recordedGame => recordedGame.Game) - .ToList(); + .ToListAsync(); } - public virtual List GetGameRecordsByGameWithUser(int gameId) + public virtual async Task> GetGameRecordsByGameWithUser(int gameId) { - return dbContext.GameRecords + return await dbContext.GameRecords .OrderBy(r => r.Started) .Where(r => r.Game.Id == gameId) .Include(recordedGame => recordedGame.User) - .ToList(); + .ToListAsync(); } - public virtual GameRecord GetRecordedGameById(int recordedGameId) + public virtual async Task GetRecordedGameById(int recordedGameId) { - return dbContext.GameRecords.Include(recordedGame => recordedGame.User) + return await dbContext.GameRecords.Include(recordedGame => recordedGame.User) .Include(r => r.Game) - .SingleOrDefault(g => g.Id == recordedGameId) + .SingleOrDefaultAsync(g => g.Id == recordedGameId) ?? throw new NotFoundException("Game record not found"); } - public virtual double CountTotalStorageMb() + public virtual async Task CountTotalStorageMb() { - return dbContext.GameRecords - .Select(r => r.SizeMb) - .ToList() + return (await dbContext.GameRecords + .Select(r => r.SizeMb) + .ToListAsync()) .Sum(); } - public virtual int CountAllGameRecords() + public virtual async Task CountAllGameRecords() { - return dbContext.GameRecords.Count(); + return await dbContext.GameRecords.CountAsync(); } public virtual void PerformGameRecordTransaction(Game game, GameRecord gameRecord, @@ -92,4 +107,38 @@ public virtual void PerformGameRecordTransaction(Game game, GameRecord gameRecor throw; } } + + // + + private static IQueryable FilterGameRecords( + bool? includeEmptyRecords, + DateTime? endDateFrom, + DateTime? endDateTo, + IQueryable query + ) + { + if (includeEmptyRecords is null or false) + query = query.Where(u => u.IsEmptyRecord == false); + if (endDateFrom.HasValue) + query = query.Where(u => u.Ended >= endDateFrom); + if (endDateTo.HasValue) + query = query.Where(u => u.Ended <= endDateTo); + + return query; + } + + private static IQueryable SortGameRecords( + SortDirection sortDirection, + GameRecordSortByFields sortBy, + IQueryable query + ) + { + return sortBy switch + { + GameRecordSortByFields.Id => DataSortingUtil.ApplySorting(query, x => x.Id, sortDirection), + GameRecordSortByFields.Ended => DataSortingUtil.ApplySorting(query, x => x.Ended, sortDirection), + GameRecordSortByFields.SizeMb => DataSortingUtil.ApplySorting(query, x => x.SizeMb, sortDirection), + _ => query + }; + } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Dao/RefreshTokenDao.cs b/rag-2-backend/Infrastructure/Dao/RefreshTokenDao.cs index a7f11c6..47a911d 100644 --- a/rag-2-backend/Infrastructure/Dao/RefreshTokenDao.cs +++ b/rag-2-backend/Infrastructure/Dao/RefreshTokenDao.cs @@ -1,5 +1,6 @@ #region +using Microsoft.EntityFrameworkCore; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -9,17 +10,17 @@ namespace rag_2_backend.Infrastructure.Dao; public class RefreshTokenDao(DatabaseContext context) { - public virtual void RemoveTokensForUser(User user) + public virtual async Task RemoveTokensForUser(User user) { - var unusedTokens = context.RefreshTokens.Where(r => r.User.Id == user.Id).ToList(); + var unusedTokens = await context.RefreshTokens.Where(r => r.User.Id == user.Id).ToListAsync(); context.RefreshTokens.RemoveRange(unusedTokens); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public virtual void RemoveTokenByToken(string token) + public virtual async Task RemoveTokenByToken(string token) { - var unusedTokens = context.RefreshTokens.Where(r => r.Token == token).ToList(); + var unusedTokens = await context.RefreshTokens.Where(r => r.Token == token).ToListAsync(); context.RefreshTokens.RemoveRange(unusedTokens); - context.SaveChanges(); + await context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Dao/UserDao.cs b/rag-2-backend/Infrastructure/Dao/UserDao.cs index 70c29f1..b97d5ee 100644 --- a/rag-2-backend/Infrastructure/Dao/UserDao.cs +++ b/rag-2-backend/Infrastructure/Dao/UserDao.cs @@ -11,22 +11,22 @@ namespace rag_2_backend.Infrastructure.Dao; public class UserDao(DatabaseContext context) { - public virtual User GetUserByIdOrThrow(int id) + public virtual async Task GetUserByIdOrThrow(int id) { - return context.Users.SingleOrDefault(u => u.Id == id) ?? + return await context.Users.SingleOrDefaultAsync(u => u.Id == id) ?? throw new NotFoundException("User not found"); } - public virtual User GetUserByEmailOrThrow(string email) + public virtual async Task GetUserByEmailOrThrow(string email) { - return context.Users + return await context.Users .Include(u => u.Course) - .SingleOrDefault(u => u.Email == email) ?? + .SingleOrDefaultAsync(u => u.Email == email) ?? throw new NotFoundException("User not found"); } - public virtual int CountUsers() + public virtual async Task CountUsers() { - return context.Users.Count(); + return await context.Users.CountAsync(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Database/Entity/AccountConfirmationToken.cs b/rag-2-backend/Infrastructure/Database/Entity/AccountConfirmationToken.cs index d733b76..90aa6cf 100644 --- a/rag-2-backend/Infrastructure/Database/Entity/AccountConfirmationToken.cs +++ b/rag-2-backend/Infrastructure/Database/Entity/AccountConfirmationToken.cs @@ -12,5 +12,6 @@ public class AccountConfirmationToken { [Key] [MaxLength(100)] public required string Token { get; init; } public required DateTime Expiration { get; set; } - public required User User { get; init; } + + [ForeignKey("UserId")] public required User User { get; init; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Database/Entity/GameRecord.cs b/rag-2-backend/Infrastructure/Database/Entity/GameRecord.cs index 58ffab7..92dfe1e 100644 --- a/rag-2-backend/Infrastructure/Database/Entity/GameRecord.cs +++ b/rag-2-backend/Infrastructure/Database/Entity/GameRecord.cs @@ -12,8 +12,11 @@ namespace rag_2_backend.Infrastructure.Database.Entity; public class GameRecord { [Key] public int Id { get; init; } - public required Game Game { get; init; } - public required User User { get; init; } + + [ForeignKey("GameId")] public required Game Game { get; init; } + + [ForeignKey("UserId")] public required User User { get; init; } + public required List Values { get; init; } public List? Players { get; init; } public DateTime Started { get; set; } diff --git a/rag-2-backend/Infrastructure/Database/Entity/PasswordResetToken.cs b/rag-2-backend/Infrastructure/Database/Entity/PasswordResetToken.cs index 5985081..f3b824b 100644 --- a/rag-2-backend/Infrastructure/Database/Entity/PasswordResetToken.cs +++ b/rag-2-backend/Infrastructure/Database/Entity/PasswordResetToken.cs @@ -12,5 +12,6 @@ public class PasswordResetToken { [Key] [MaxLength(100)] public required string Token { get; init; } public required DateTime Expiration { get; set; } - public required User User { get; init; } + + [ForeignKey("UserId")] public required User User { get; init; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Database/Entity/RefreshToken.cs b/rag-2-backend/Infrastructure/Database/Entity/RefreshToken.cs index 652c180..4696e13 100644 --- a/rag-2-backend/Infrastructure/Database/Entity/RefreshToken.cs +++ b/rag-2-backend/Infrastructure/Database/Entity/RefreshToken.cs @@ -12,5 +12,6 @@ public class RefreshToken { [Key] [MaxLength(100)] public required string Token { get; init; } public required DateTime Expiration { get; init; } - public required User User { get; init; } + + [ForeignKey("UserId")] public required User User { get; init; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Database/Entity/User.cs b/rag-2-backend/Infrastructure/Database/Entity/User.cs index a48a531..2e53d3b 100644 --- a/rag-2-backend/Infrastructure/Database/Entity/User.cs +++ b/rag-2-backend/Infrastructure/Database/Entity/User.cs @@ -47,6 +47,8 @@ public User(string email) public int StudyCycleYearB { get; set; } public bool Banned { get; set; } public DateTime LastPlayed { get; set; } - public Course? Course { get; set; } + + [ForeignKey("CourseId")] public Course? Course { get; set; } + [MaxLength(100)] public string? Group { get; set; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Administration/AdministrationController.cs b/rag-2-backend/Infrastructure/Module/Administration/AdministrationController.cs index c78c534..e7623cf 100644 --- a/rag-2-backend/Infrastructure/Module/Administration/AdministrationController.cs +++ b/rag-2-backend/Infrastructure/Module/Administration/AdministrationController.cs @@ -23,9 +23,9 @@ public class AdministrationController( /// Cannot ban administrator [HttpPost("{userId:int}/ban-status")] [Authorize(Roles = "Admin")] - public void ChangeBanStatus([Required] int userId, [Required] bool isBanned) + public async Task ChangeBanStatus([Required] int userId, [Required] bool isBanned) { - administrationService.ChangeBanStatus(userId, isBanned); + await administrationService.ChangeBanStatus(userId, isBanned); } /// Change role for any user by user ID despite admins (Admin) @@ -33,18 +33,18 @@ public void ChangeBanStatus([Required] int userId, [Required] bool isBanned) /// Cannot change administrator's role [HttpPost("{userId:int}/role")] [Authorize(Roles = "Admin")] - public void ChangeRole([Required] int userId, [Required] Role role) + public async Task ChangeRole([Required] int userId, [Required] Role role) { - administrationService.ChangeRole(userId, role); + await administrationService.ChangeRole(userId, role); } /// Get details of any user by user ID (Admin, Teacher) /// Cannot view details [HttpGet("{userId:int}/details")] [Authorize(Roles = "Admin,Teacher")] - public UserResponse GetUserDetails([Required] int userId) + public async Task GetUserDetails([Required] int userId) { - return administrationService.GetUserDetails(AuthDao.GetPrincipalEmail(User), userId); + return await administrationService.GetUserDetails(AuthDao.GetPrincipalEmail(User), userId); } /// Get current limits for roles (Auth) @@ -61,11 +61,29 @@ public LimitsResponse GetCurrentLimits() }; } - /// Get all users list (Admin, Teacher) + /// Get all users list with optional filters and sorting (Admin, Teacher) [HttpGet("users")] [Authorize(Roles = "Admin, Teacher")] - public List GetStudents() + public async Task> GetUsers( + [Required] Role role, + string? email, + int? studyCycleYearA, + int? studyCycleYearB, + string? group, + string? courseName, + SortDirection sortDirection = SortDirection.Asc, + UserSortByFields sortBy = UserSortByFields.Id + ) { - return administrationService.GetStudents(); + return await administrationService.GetUsers( + role, + email, + studyCycleYearA, + studyCycleYearB, + group, + courseName, + sortDirection, + sortBy + ); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Administration/AdministrationService.cs b/rag-2-backend/Infrastructure/Module/Administration/AdministrationService.cs index 8563865..93b3796 100644 --- a/rag-2-backend/Infrastructure/Module/Administration/AdministrationService.cs +++ b/rag-2-backend/Infrastructure/Module/Administration/AdministrationService.cs @@ -1,11 +1,14 @@ #region using HttpExceptions.Exceptions; +using Microsoft.EntityFrameworkCore; using rag_2_backend.Infrastructure.Common.Mapper; using rag_2_backend.Infrastructure.Common.Model; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; +using rag_2_backend.Infrastructure.Module.Administration.Dto; using rag_2_backend.Infrastructure.Module.User.Dto; +using rag_2_backend.Infrastructure.Util; #endregion @@ -13,42 +16,99 @@ namespace rag_2_backend.Infrastructure.Module.Administration; public class AdministrationService(DatabaseContext context, UserDao userDao) { - public void ChangeBanStatus(int userId, bool isBanned) + public async Task ChangeBanStatus(int userId, bool isBanned) { - var user = userDao.GetUserByIdOrThrow(userId); + var user = await userDao.GetUserByIdOrThrow(userId); if (user.Role == Role.Admin) throw new BadRequestException("Cannot ban administrator"); user.Banned = isBanned; - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void ChangeRole(int userId, Role role) + public async Task ChangeRole(int userId, Role role) { - var user = userDao.GetUserByIdOrThrow(userId); + var user = await userDao.GetUserByIdOrThrow(userId); if (user.Role == Role.Admin) throw new BadRequestException("Cannot change administrator's role"); user.Role = role; - context.SaveChanges(); + await context.SaveChangesAsync(); } - public UserResponse GetUserDetails(string principalEmail, int userId) + public async Task GetUserDetails(string principalEmail, int userId) { - var principal = userDao.GetUserByEmailOrThrow(principalEmail); + var principal = await userDao.GetUserByEmailOrThrow(principalEmail); if (principal.Role is Role.Student && userId != principal.Id) throw new ForbiddenException("Cannot view details"); - return UserMapper.Map(userDao.GetUserByIdOrThrow(userId)); + return UserMapper.Map(await userDao.GetUserByIdOrThrow(userId)); } - public List GetStudents() + public async Task> GetUsers( + Role role, + string? email, + int? studyCycleYearA, + int? studyCycleYearB, + string? group, + string? courseName, + SortDirection sortDirection, + UserSortByFields sortBy + ) { - return context.Users - .Select(u => UserMapper.Map(u)) - .ToList(); + var query = context.Users + .Include(u => u.Course) + .Where(u => u.Role == role) + .AsQueryable(); + + query = FilterUsers(email, studyCycleYearA, studyCycleYearB, group, courseName, query); + query = SortUsers(sortDirection, sortBy, query); + + return await Task.Run(() => query.AsEnumerable().Select(UserMapper.Map).ToList()); + } + + // + + private static IQueryable FilterUsers(string? email, int? studyCycleYearA, + int? studyCycleYearB, string? group, + string? courseName, IQueryable query) + { + if (!string.IsNullOrEmpty(email)) + query = query.Where(u => u.Email.ToLower().Contains(email.ToLower())); + if (studyCycleYearA.HasValue) + query = query.Where(u => u.StudyCycleYearA == studyCycleYearA.Value); + if (studyCycleYearB.HasValue) + query = query.Where(u => u.StudyCycleYearB == studyCycleYearB.Value); + if (!string.IsNullOrEmpty(group)) + query = query.Where(u => u.Group != null && u.Group.ToLower().Contains( + group.ToLower() + )); + if (!string.IsNullOrEmpty(courseName)) + query = query.Where(u => u.Course != null && u.Course.Name.Equals(courseName)); + return query; + } + + private static IQueryable SortUsers(SortDirection sortDirection, UserSortByFields sortBy, + IQueryable query) + { + return sortBy switch + { + UserSortByFields.Id => DataSortingUtil.ApplySorting(query, x => x.Id, sortDirection), + UserSortByFields.Email => DataSortingUtil.ApplySorting(query, x => x.Email, sortDirection), + UserSortByFields.Name => DataSortingUtil.ApplySorting(query, x => x.Name, sortDirection), + UserSortByFields.StudyYearCycleA => DataSortingUtil.ApplySorting(query, x => x.StudyCycleYearA, + sortDirection), + UserSortByFields.StudyYearCycleB => DataSortingUtil.ApplySorting(query, x => x.StudyCycleYearB, + sortDirection), + UserSortByFields.LastPlayed => DataSortingUtil.ApplySorting(query, x => x.LastPlayed, sortDirection), + UserSortByFields.CourseName => DataSortingUtil.ApplySorting(query, + x => x.Course != null ? x.Course.Name : string.Empty, + sortDirection), + UserSortByFields.Group => DataSortingUtil.ApplySorting(query, x => x.Group, sortDirection), + _ => query + }; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Administration/Dto/UserSortByFields.cs b/rag-2-backend/Infrastructure/Module/Administration/Dto/UserSortByFields.cs new file mode 100644 index 0000000..d54e216 --- /dev/null +++ b/rag-2-backend/Infrastructure/Module/Administration/Dto/UserSortByFields.cs @@ -0,0 +1,13 @@ +namespace rag_2_backend.Infrastructure.Module.Administration.Dto; + +public enum UserSortByFields +{ + Id, + Email, + Name, + StudyYearCycleA, + StudyYearCycleB, + LastPlayed, + CourseName, + Group +} \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Auth/AuthController.cs b/rag-2-backend/Infrastructure/Module/Auth/AuthController.cs index 644c1f1..c132245 100644 --- a/rag-2-backend/Infrastructure/Module/Auth/AuthController.cs +++ b/rag-2-backend/Infrastructure/Module/Auth/AuthController.cs @@ -20,20 +20,21 @@ public class AuthController(IConfiguration config, AuthService authService) : Co /// Token invalid [HttpGet("verify")] [Authorize] - public void VerifyToken() + public Task VerifyToken() { + return Task.CompletedTask; } /// Authenticate /// Invalid password or mail not confirmed or user banned [HttpPost("login")] - public string Login([FromBody] [Required] UserLoginRequest loginRequest) + public async Task Login([FromBody] [Required] UserLoginRequest loginRequest) { var refreshTokenExpiryDays = loginRequest.RememberMe ? double.Parse(config["RefreshToken:ExpireDaysRememberMe"] ?? "10") : double.Parse(config["RefreshToken:ExpireDays"] ?? "1"); - var response = authService.LoginUser( + var response = await authService.LoginUser( loginRequest.Email, loginRequest.Password, refreshTokenExpiryDays @@ -55,31 +56,31 @@ public string Login([FromBody] [Required] UserLoginRequest loginRequest) /// Refresh token /// Invalid refresh token [HttpPost("refresh-token")] - public string RefreshToken() + public async Task RefreshToken() { HttpContext.Request.Cookies.TryGetValue("refreshToken", out var refreshToken); if (refreshToken == null) throw new UnauthorizedException("Invalid refresh token"); - return authService.RefreshToken(refreshToken); + return await authService.RefreshToken(refreshToken); } /// Logout current user (Auth) [HttpPost("logout")] [Authorize] - public void Logout() + public async Task Logout() { HttpContext.Request.Cookies.TryGetValue("refreshToken", out var refreshToken); if (refreshToken != null) - authService.LogoutUser(refreshToken); + await authService.LogoutUser(refreshToken); } /// Get current user details (Auth) [HttpGet("me")] [Authorize] - public UserResponse Me() + public async Task Me() { - return authService.GetMe(AuthDao.GetPrincipalEmail(User)); + return await authService.GetMe(AuthDao.GetPrincipalEmail(User)); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Auth/AuthService.cs b/rag-2-backend/Infrastructure/Module/Auth/AuthService.cs index dc296ba..3234ff0 100644 --- a/rag-2-backend/Infrastructure/Module/Auth/AuthService.cs +++ b/rag-2-backend/Infrastructure/Module/Auth/AuthService.cs @@ -21,9 +21,9 @@ public class AuthService( JwtUtil jwtUtil ) { - public UserLoginResponse LoginUser(string email, string password, double refreshTokenExpirationTimeDays) + public async Task LoginUser(string email, string password, double refreshTokenExpirationTimeDays) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); if (!HashUtil.VerifyPassword(password, user.Password)) throw new UnauthorizedException("Invalid password"); @@ -32,7 +32,7 @@ public UserLoginResponse LoginUser(string email, string password, double refresh if (user.Banned) throw new UnauthorizedException("User banned"); - var refreshToken = GenerateRefreshToken(refreshTokenExpirationTimeDays, user); + var refreshToken = await GenerateRefreshToken(refreshTokenExpirationTimeDays, user); return new UserLoginResponse { @@ -41,30 +41,31 @@ public UserLoginResponse LoginUser(string email, string password, double refresh }; } - public string RefreshToken(string refreshToken) + public async Task RefreshToken(string refreshToken) { - var token = databaseContext.RefreshTokens + var token = await databaseContext.RefreshTokens .Include(t => t.User) - .SingleOrDefault(t => t.Token == refreshToken && t.Expiration > DateTime.Now) + .SingleOrDefaultAsync(t => t.Token == refreshToken && t.Expiration > DateTime.Now) ?? throw new UnauthorizedException("Invalid refresh token"); var user = token.User; return jwtUtil.GenerateJwt(user.Email, user.Role.ToString()); } - public UserResponse GetMe(string email) + public async Task GetMe(string email) { - return UserMapper.Map(userDao.GetUserByEmailOrThrow(email)); + return UserMapper.Map(await userDao.GetUserByEmailOrThrow(email)); } - public void LogoutUser(string token) + public async Task LogoutUser(string token) { - refreshTokenDao.RemoveTokenByToken(token); + await refreshTokenDao.RemoveTokenByToken(token); } // - private RefreshToken GenerateRefreshToken(double refreshTokenExpirationTimeDays, Database.Entity.User user) + private async Task GenerateRefreshToken(double refreshTokenExpirationTimeDays, + Database.Entity.User user) { var refreshToken = new RefreshToken { @@ -72,8 +73,8 @@ private RefreshToken GenerateRefreshToken(double refreshTokenExpirationTimeDays, Expiration = DateTime.Now.AddDays(refreshTokenExpirationTimeDays), Token = Guid.NewGuid().ToString() }; - databaseContext.RefreshTokens.Add(refreshToken); - databaseContext.SaveChanges(); + await databaseContext.RefreshTokens.AddAsync(refreshToken); + await databaseContext.SaveChangesAsync(); return refreshToken; } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Background/BackgroundServiceImpl.cs b/rag-2-backend/Infrastructure/Module/Background/BackgroundServiceImpl.cs index 8778c53..fba5153 100644 --- a/rag-2-backend/Infrastructure/Module/Background/BackgroundServiceImpl.cs +++ b/rag-2-backend/Infrastructure/Module/Background/BackgroundServiceImpl.cs @@ -23,10 +23,10 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) while (!cancellationToken.IsCancellationRequested) { - DeleteUnusedAccountTokens(); - DeleteUnusedRefreshTokens(); - DeleteUnusedPasswordResetTokens(); - UpdateCachedStats(); + await DeleteUnusedAccountTokens(); + await DeleteUnusedRefreshTokens(); + await DeleteUnusedPasswordResetTokens(); + await UpdateCachedStats(); await Task.Delay(TimeSpan.FromHours(3), cancellationToken); } @@ -34,12 +34,12 @@ protected override async Task ExecuteAsync(CancellationToken cancellationToken) // - private void DeleteUnusedAccountTokens() + private async Task DeleteUnusedAccountTokens() { var unconfirmedUsers = new List(); - var unusedTokens = _dbContext.AccountConfirmationTokens + var unusedTokens = await _dbContext.AccountConfirmationTokens .Include(t => t.User) - .Where(t => t.Expiration < DateTime.Now).ToList(); + .Where(t => t.Expiration < DateTime.Now).ToListAsync(); foreach (var users in unusedTokens.Select(token => new List(_dbContext.Users.Where(u => @@ -48,35 +48,35 @@ private void DeleteUnusedAccountTokens() _dbContext.Users.RemoveRange(unconfirmedUsers); _dbContext.AccountConfirmationTokens.RemoveRange(unusedTokens); - _dbContext.SaveChanges(); + await _dbContext.SaveChangesAsync(); Console.WriteLine("Deleted " + unconfirmedUsers.Count + " unconfirmed accounts with tokens"); } - private void DeleteUnusedRefreshTokens() + private async Task DeleteUnusedRefreshTokens() { var unusedTokens = _dbContext.RefreshTokens.Where(b => b.Expiration < DateTime.Now).ToList(); _dbContext.RefreshTokens.RemoveRange(unusedTokens); - _dbContext.SaveChanges(); + await _dbContext.SaveChangesAsync(); Console.WriteLine("Deleted " + unusedTokens.Count + " expired refresh tokens"); } - private void DeleteUnusedPasswordResetTokens() + private async Task DeleteUnusedPasswordResetTokens() { var unusedTokens = _dbContext.PasswordResetTokens.Where(b => b.Expiration < DateTime.Now).ToList(); _dbContext.PasswordResetTokens.RemoveRange(unusedTokens); - _dbContext.SaveChanges(); + await _dbContext.SaveChangesAsync(); Console.WriteLine("Deleted " + unusedTokens.Count + " expired password reset tokens"); } - private async void UpdateCachedStats() + private async Task UpdateCachedStats() { var games = await _dbContext.Games.ToListAsync(); - foreach (var game in games) _statsUtil.UpdateCachedGameStats(game); + foreach (var game in games) await _statsUtil.UpdateCachedGameStats(game); - _statsUtil.UpdateCachedStats(); + await _statsUtil.UpdateCachedStats(); Console.WriteLine("Stats updated."); } diff --git a/rag-2-backend/Infrastructure/Module/Course/CourseController.cs b/rag-2-backend/Infrastructure/Module/Course/CourseController.cs index 472df8b..5eddb14 100644 --- a/rag-2-backend/Infrastructure/Module/Course/CourseController.cs +++ b/rag-2-backend/Infrastructure/Module/Course/CourseController.cs @@ -24,9 +24,9 @@ public async Task> GetCourses() /// Course with this name already exists [HttpPost] [Authorize(Roles = "Admin")] - public void Add([FromBody] [Required] CourseRequest request) + public async Task Add([FromBody] [Required] CourseRequest request) { - courseService.AddCourse(request); + await courseService.AddCourse(request); } /// Edit existing course (Admin) @@ -34,9 +34,9 @@ public void Add([FromBody] [Required] CourseRequest request) /// Course with this name already exists [HttpPut("{id:int}")] [Authorize(Roles = "Admin")] - public void Edit([FromBody] [Required] CourseRequest request, int id) + public async Task Edit([FromBody] [Required] CourseRequest request, int id) { - courseService.EditCourse(request, id); + await courseService.EditCourse(request, id); } /// Remove existing course (Admin) @@ -44,8 +44,8 @@ public void Edit([FromBody] [Required] CourseRequest request, int id) /// Cannot delete used course [HttpDelete("{id:int}")] [Authorize(Roles = "Admin")] - public void Remove([Required] int id) + public async Task Remove([Required] int id) { - courseService.RemoveCourse(id); + await courseService.RemoveCourse(id); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Course/CourseService.cs b/rag-2-backend/Infrastructure/Module/Course/CourseService.cs index 3f3e12a..dae253b 100644 --- a/rag-2-backend/Infrastructure/Module/Course/CourseService.cs +++ b/rag-2-backend/Infrastructure/Module/Course/CourseService.cs @@ -13,16 +13,16 @@ namespace rag_2_backend.Infrastructure.Module.Course; public class CourseService(DatabaseContext context, CourseDao courseDao) { - public async Task> GetCourses() + public async Task> GetCourses() { var courses = await context.Courses.ToListAsync(); - return courses.Select(CourseMapper.Map); + return courses.Select(CourseMapper.Map).ToList(); } - public void AddCourse(CourseRequest request) + public async Task AddCourse(CourseRequest request) { - if (context.Courses.Any(c => c.Name == request.Name)) + if (await context.Courses.AnyAsync(c => c.Name == request.Name)) throw new BadRequestException("Course with this name already exists"); var course = new Database.Entity.Course @@ -30,34 +30,34 @@ public void AddCourse(CourseRequest request) Name = request.Name }; - context.Courses.Add(course); - context.SaveChanges(); + await context.Courses.AddAsync(course); + await context.SaveChangesAsync(); } - public void EditCourse(CourseRequest request, int id) + public async Task EditCourse(CourseRequest request, int id) { - var course = courseDao.GetCourseByIdOrThrow(id); + var course = await courseDao.GetCourseByIdOrThrow(id); - if (context.Courses.Any(c => c.Name == request.Name && c.Name != course.Name)) + if (await context.Courses.AnyAsync(c => c.Name == request.Name && c.Name != course.Name)) throw new BadRequestException("Course with this name already exists"); course.Name = request.Name; context.Courses.Update(course); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void RemoveCourse(int id) + public async Task RemoveCourse(int id) { - var course = courseDao.GetCourseByIdOrThrow(id); + var course = await courseDao.GetCourseByIdOrThrow(id); - var usersWithCourses = context.Users - .Include(u => u.Course).Count(u => u.Course != null && u.Course.Id == id); + var usersWithCourses = await context.Users + .Include(u => u.Course).CountAsync(u => u.Course != null && u.Course.Id == id); if (usersWithCourses > 0) throw new BadRequestException("Cannot delete used course"); context.Courses.Remove(course); - context.SaveChanges(); + await context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Game/GameController.cs b/rag-2-backend/Infrastructure/Module/Game/GameController.cs index 969aaac..fdbc1f8 100644 --- a/rag-2-backend/Infrastructure/Module/Game/GameController.cs +++ b/rag-2-backend/Infrastructure/Module/Game/GameController.cs @@ -24,9 +24,9 @@ public async Task> GetGames() /// Game with this name already exists [HttpPost] [Authorize(Roles = "Admin")] - public void Add([FromBody] [Required] GameRequest request) + public async Task Add([FromBody] [Required] GameRequest request) { - gameService.AddGame(request); + await gameService.AddGame(request); } /// Edit existing game (Admin) @@ -34,17 +34,17 @@ public void Add([FromBody] [Required] GameRequest request) /// Game with this name already exists [HttpPut("{id:int}")] [Authorize(Roles = "Admin")] - public void Edit([FromBody] [Required] GameRequest request, int id) + public async Task Edit([FromBody] [Required] GameRequest request, int id) { - gameService.EditGame(request, id); + await gameService.EditGame(request, id); } /// Remove existing game (Admin) /// Game not found [HttpDelete("{id:int}")] [Authorize(Roles = "Admin")] - public void Remove([Required] int id) + public async Task Remove([Required] int id) { - gameService.RemoveGame(id); + await gameService.RemoveGame(id); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Game/GameService.cs b/rag-2-backend/Infrastructure/Module/Game/GameService.cs index 4d9c06e..cd80ad2 100644 --- a/rag-2-backend/Infrastructure/Module/Game/GameService.cs +++ b/rag-2-backend/Infrastructure/Module/Game/GameService.cs @@ -20,9 +20,9 @@ public async Task> GetGames() return games.Select(GameMapper.Map); } - public void AddGame(GameRequest request) + public async Task AddGame(GameRequest request) { - if (context.Games.Any(g => g.Name == request.Name)) + if (await context.Games.AnyAsync(g => g.Name == request.Name)) throw new BadRequestException("Game with this name already exists"); var game = new Database.Entity.Game @@ -31,32 +31,32 @@ public void AddGame(GameRequest request) Description = request.Description }; - context.Games.Add(game); - context.SaveChanges(); + await context.Games.AddAsync(game); + await context.SaveChangesAsync(); } - public void EditGame(GameRequest request, int id) + public async Task EditGame(GameRequest request, int id) { - var game = gameDao.GetGameByIdOrThrow(id); + var game = await gameDao.GetGameByIdOrThrow(id); - if (context.Games.Any(g => g.Name == request.Name && g.Name != game.Name)) + if (await context.Games.AnyAsync(g => g.Name == request.Name && g.Name != game.Name)) throw new BadRequestException("Game with this name already exists"); game.Name = request.Name; game.Description = request.Description; context.Games.Update(game); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void RemoveGame(int id) + public async Task RemoveGame(int id) { - var game = gameDao.GetGameByIdOrThrow(id); + var game = await gameDao.GetGameByIdOrThrow(id); - var records = context.GameRecords.Where(g => g.Game.Id == id).ToList(); + var records = await context.GameRecords.Where(g => g.Game.Id == id).ToListAsync(); foreach (var record in records) context.GameRecords.Remove(record); context.Games.Remove(game); - context.SaveChanges(); + await context.SaveChangesAsync(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/GameRecord/Dto/GameRecordSortByFields.cs b/rag-2-backend/Infrastructure/Module/GameRecord/Dto/GameRecordSortByFields.cs new file mode 100644 index 0000000..d4b3617 --- /dev/null +++ b/rag-2-backend/Infrastructure/Module/GameRecord/Dto/GameRecordSortByFields.cs @@ -0,0 +1,8 @@ +namespace rag_2_backend.Infrastructure.Module.GameRecord.Dto; + +public enum GameRecordSortByFields +{ + Id, + Ended, + SizeMb +} \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordController.cs b/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordController.cs index f169096..709d214 100644 --- a/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordController.cs +++ b/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordController.cs @@ -3,6 +3,7 @@ using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using rag_2_backend.Infrastructure.Common.Model; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Module.GameRecord.Dto; @@ -18,11 +19,28 @@ public class GameRecordController(GameRecordService gameRecordService) : Control /// User or game not found [HttpGet] [Authorize] - public List GetRecordsByGame([Required] int gameId, [Required] int userId) + public async Task> GetRecordsByGame( + [Required] int gameId, + [Required] int userId, + bool? includeEmptyRecords, + DateTime? endDateFrom, + DateTime? endDateTo, + SortDirection sortDirection = SortDirection.Asc, + GameRecordSortByFields sortBy = GameRecordSortByFields.Id + ) { var email = AuthDao.GetPrincipalEmail(User); - return gameRecordService.GetRecordsByGameAndUser(gameId, userId, email); + return await gameRecordService.GetRecordsByGameAndUser( + gameId, + userId, + includeEmptyRecords, + endDateFrom, + endDateTo, + sortDirection, + sortBy, + email + ); } /// Download JSON file from specific game, admin and teacher can download everyone's data (Auth) @@ -31,11 +49,11 @@ public List GetRecordsByGame([Required] int gameId, [Require /// Record is empty [HttpGet("{recordedGameId:int}")] [Authorize] - public FileContentResult DownloadRecordData([Required] int recordedGameId) + public async Task DownloadRecordData([Required] int recordedGameId) { var email = AuthDao.GetPrincipalEmail(User); var fileName = "game_record_" + recordedGameId + "_" + email + ".json"; - var fileStream = gameRecordService.DownloadRecordData(recordedGameId, email); + var fileStream = await gameRecordService.DownloadRecordData(recordedGameId, email); return File(fileStream, "application/json", fileName); } @@ -45,9 +63,9 @@ public FileContentResult DownloadRecordData([Required] int recordedGameId) /// Space limit exceeded [HttpPost] [Authorize] - public void AddGameRecord([FromBody] [Required] GameRecordRequest recordRequest) + public async Task AddGameRecord([FromBody] [Required] GameRecordRequest recordRequest) { - gameRecordService.AddGameRecord(recordRequest, AuthDao.GetPrincipalEmail(User)); + await gameRecordService.AddGameRecord(recordRequest, AuthDao.GetPrincipalEmail(User)); } /// Remove game recording (Auth) @@ -55,8 +73,8 @@ public void AddGameRecord([FromBody] [Required] GameRecordRequest recordRequest) /// Permission denied [HttpDelete] [Authorize] - public void RemoveGameRecord([Required] int recordedGameId) + public async Task RemoveGameRecord([Required] int recordedGameId) { - gameRecordService.RemoveGameRecord(recordedGameId, AuthDao.GetPrincipalEmail(User)); + await gameRecordService.RemoveGameRecord(recordedGameId, AuthDao.GetPrincipalEmail(User)); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordService.cs b/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordService.cs index 79eebee..b9b3dd0 100644 --- a/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordService.cs +++ b/rag-2-backend/Infrastructure/Module/GameRecord/GameRecordService.cs @@ -23,20 +23,37 @@ public class GameRecordService( GameDao gameDao ) { - public List GetRecordsByGameAndUser(int gameId, int userId, string email) + public async Task> GetRecordsByGameAndUser( + int gameId, + int userId, + bool? includeEmptyRecords, + DateTime? endDateFrom, + DateTime? endDateTo, + SortDirection sortDirection, + GameRecordSortByFields sortBy, + string email + ) { - var principal = userDao.GetUserByEmailOrThrow(email); + var principal = await userDao.GetUserByEmailOrThrow(email); if (principal.Id != userId && principal.Role.Equals(Role.Student)) throw new BadRequestException("Permission denied"); - return gameRecordDao.GetRecordsByGameAndUser(gameId, userId); + return await gameRecordDao.GetRecordsByGameAndUser( + gameId, + userId, + includeEmptyRecords, + endDateFrom, + endDateTo, + sortDirection, + sortBy + ); } - public byte[] DownloadRecordData(int recordedGameId, string email) + public async Task DownloadRecordData(int recordedGameId, string email) { - var user = userDao.GetUserByEmailOrThrow(email); - var recordedGame = gameRecordDao.GetRecordedGameById(recordedGameId); + var user = await userDao.GetUserByEmailOrThrow(email); + var recordedGame = await gameRecordDao.GetRecordedGameById(recordedGameId); if (user.Id != recordedGame.User.Id && user.Role.Equals(Role.Student)) throw new ForbiddenException("Permission denied"); @@ -46,13 +63,13 @@ public byte[] DownloadRecordData(int recordedGameId, string email) return Encoding.UTF8.GetBytes(JsonSerializer.Serialize(GameRecordMapper.JsonMap(recordedGame))); } - public void AddGameRecord(GameRecordRequest recordRequest, string email) + public async Task AddGameRecord(GameRecordRequest recordRequest, string email) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); if (recordRequest.Values.Count > 0) - CheckUserDataLimit(recordRequest, user); + await CheckUserDataLimit(recordRequest, user); - var game = gameDao.GetGameByNameOrThrow(recordRequest.GameName); + var game = await gameDao.GetGameByNameOrThrow(recordRequest.GameName); var recordedGame = new Database.Entity.GameRecord { @@ -74,24 +91,24 @@ public void AddGameRecord(GameRecordRequest recordRequest, string email) executionStrategy.Execute(() => gameRecordDao.PerformGameRecordTransaction(game, recordedGame, user)); } - public void RemoveGameRecord(int gameRecordId, string email) + public async Task RemoveGameRecord(int gameRecordId, string email) { - var user = userDao.GetUserByEmailOrThrow(email); - var recordedGame = gameRecordDao.GetRecordedGameById(gameRecordId); + var user = await userDao.GetUserByEmailOrThrow(email); + var recordedGame = await gameRecordDao.GetRecordedGameById(gameRecordId); if (user.Id != recordedGame.User.Id) throw new BadRequestException("Permission denied"); context.GameRecords.Remove(recordedGame); - context.SaveChanges(); + await context.SaveChangesAsync(); } // - private void CheckUserDataLimit(GameRecordRequest recordRequest, Database.Entity.User user) + private async Task CheckUserDataLimit(GameRecordRequest recordRequest, Database.Entity.User user) { var initialSizeMb = JsonSerializer.Serialize(recordRequest.Values).Length / (1024.0 * 1024.0); - var totalSizeMb = GetSizeByUser(user.Id, initialSizeMb); + var totalSizeMb = await GetSizeByUser(user.Id, initialSizeMb); switch (user.Role) { @@ -109,9 +126,9 @@ private void CheckUserDataLimit(GameRecordRequest recordRequest, Database.Entity } } - private double GetSizeByUser(int userId, double initialSizeBytes) + private async Task GetSizeByUser(int userId, double initialSizeBytes) { - var results = gameRecordDao.GetGameRecordsByUserWithGame(userId) + var results = (await gameRecordDao.GetGameRecordsByUserWithGame(userId)) .Select(r => r.SizeMb) .ToList() .Sum(); diff --git a/rag-2-backend/Infrastructure/Module/Stats/StatsController.cs b/rag-2-backend/Infrastructure/Module/Stats/StatsController.cs index 535723f..0beaddf 100644 --- a/rag-2-backend/Infrastructure/Module/Stats/StatsController.cs +++ b/rag-2-backend/Infrastructure/Module/Stats/StatsController.cs @@ -19,23 +19,23 @@ public class StatsController(StatsService statsService) : ControllerBase /// Permission denied [HttpGet("user")] [Authorize] - public UserStatsResponse GetStatsForUser([Required] [FromQuery] int userId) + public async Task GetStatsForUser([Required] [FromQuery] int userId) { - return statsService.GetStatsForUser(AuthDao.GetPrincipalEmail(User), userId); + return await statsService.GetStatsForUser(AuthDao.GetPrincipalEmail(User), userId); } /// Get stats for game /// Game not found [HttpGet("game")] - public GameStatsResponse GetStatsForGame([Required] [FromQuery] int gameId) + public async Task GetStatsForGame([Required] [FromQuery] int gameId) { - return statsService.GetStatsForGame(gameId); + return await statsService.GetStatsForGame(gameId); } /// Get overall stats [HttpGet("all")] - public OverallStatsResponse GetOverallStats() + public async Task GetOverallStats() { - return statsService.GetOverallStats(); + return await statsService.GetOverallStats(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/Stats/StatsService.cs b/rag-2-backend/Infrastructure/Module/Stats/StatsService.cs index 5467e93..de71fdb 100644 --- a/rag-2-backend/Infrastructure/Module/Stats/StatsService.cs +++ b/rag-2-backend/Infrastructure/Module/Stats/StatsService.cs @@ -24,15 +24,15 @@ StatsUtil statsUtil { private readonly IDatabase _redisDatabase = redisConnection.GetDatabase(); - public UserStatsResponse GetStatsForUser(string email, int userId) + public async Task GetStatsForUser(string email, int userId) { - var principal = userDao.GetUserByEmailOrThrow(email); - var user = userDao.GetUserByIdOrThrow(userId); + var principal = await userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByIdOrThrow(userId); if (user.Email != email && principal.Role == Role.Student) throw new ForbiddenException("Permission denied"); - var records = gameRecordDao.GetGameRecordsByUserWithGame(userId); + var records = await gameRecordDao.GetGameRecordsByUserWithGame(userId); return new UserStatsResponse { @@ -40,26 +40,26 @@ public UserStatsResponse GetStatsForUser(string email, int userId) LastPlayed = records.Count > 0 ? records.Last().Ended : null, Games = records.Select(r => r.Game.Id).Distinct().ToList().Count, Plays = records.Count, - TotalStorageMb = gameRecordDao.GetGameRecordsByUserWithGame(userId) + TotalStorageMb = (await gameRecordDao.GetGameRecordsByUserWithGame(userId)) .Select(r => r.SizeMb) .ToList() .Sum() }; } - public GameStatsResponse GetStatsForGame(int gameId) + public async Task GetStatsForGame(int gameId) { - var game = gameDao.GetGameByIdOrThrow(gameId); + var game = await gameDao.GetGameByIdOrThrow(gameId); var cacheKey = $"{configuration.GetValue("Redis:Stats:Prefix")}{game.Id}"; var cachedStatsJson = _redisDatabase.StringGet(cacheKey); return !string.IsNullOrEmpty(cachedStatsJson) ? JsonConvert.DeserializeObject(cachedStatsJson!) - : statsUtil.UpdateCachedGameStats(game); + : await statsUtil.UpdateCachedGameStats(game); } - public OverallStatsResponse GetOverallStats() + public async Task GetOverallStats() { var cachedStatsJson = _redisDatabase.StringGet( configuration.GetValue("Redis:Stats:Prefix") + @@ -68,6 +68,6 @@ public OverallStatsResponse GetOverallStats() return !string.IsNullOrEmpty(cachedStatsJson) ? JsonConvert.DeserializeObject(cachedStatsJson!) - : statsUtil.UpdateCachedStats(); + : await statsUtil.UpdateCachedStats(); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/User/UserController.cs b/rag-2-backend/Infrastructure/Module/User/UserController.cs index de639ca..5e63bdb 100644 --- a/rag-2-backend/Infrastructure/Module/User/UserController.cs +++ b/rag-2-backend/Infrastructure/Module/User/UserController.cs @@ -17,69 +17,66 @@ public class UserController(UserService userService) : ControllerBase /// Register new user /// User already exists or wrong data [HttpPost("register")] - public void Register([FromBody] [Required] UserRequest userRequest) + public async Task Register([FromBody] [Required] UserRequest userRequest) { - userService.RegisterUser(userRequest); + await userService.RegisterUser(userRequest); } /// Resend confirmation email to specified email /// User not found /// User is already confirmed [HttpPost("resend-confirmation-email")] - public void ResendConfirmationEmail([Required] string email) + public async Task ResendConfirmationEmail([Required] string email) { - userService.ResendConfirmationEmail(email); + await userService.ResendConfirmationEmail(email); } /// Confirm account with token from mail /// Invalid token [HttpPost("confirm-account")] - public void ConfirmAccount([Required] string token) + public async Task ConfirmAccount([Required] string token) { - userService.ConfirmAccount(token); + await userService.ConfirmAccount(token); } /// Edit account info (Auth) /// Wrong data [HttpPatch("update")] [Authorize] - public void UpdateAccount([Required] UserEditRequest request) + public async Task UpdateAccount([Required] UserEditRequest request) { - userService.UpdateAccount(request, AuthDao.GetPrincipalEmail(User)); + await userService.UpdateAccount(request, AuthDao.GetPrincipalEmail(User)); } /// Request password reset for given email [HttpPost("request-password-reset")] - public void RequestPasswordReset([Required] string email) + public async Task RequestPasswordReset([Required] string email) { - userService.RequestPasswordReset(email); + await userService.RequestPasswordReset(email); } /// Reset password with token and new password /// Invalid token [HttpPost("reset-password")] - public void ResetPassword([Required] string tokenValue, [Required] string newPassword) + public async Task ResetPassword([Required] string tokenValue, [Required] string newPassword) { - userService.ResetPassword(tokenValue, newPassword); + await userService.ResetPassword(tokenValue, newPassword); } /// Change current user's password (Auth) /// Invalid old password or given the same password as old [HttpPost("change-password")] [Authorize] - public void ChangePassword([Required] string oldPassword, [Required] string newPassword) + public async Task ChangePassword([Required] string oldPassword, [Required] string newPassword) { - userService.ChangePassword(AuthDao.GetPrincipalEmail(User), oldPassword, newPassword); + await userService.ChangePassword(AuthDao.GetPrincipalEmail(User), oldPassword, newPassword); } /// Permanently delete account and all data (Auth) [HttpDelete("delete-account")] [Authorize] - public void DeleteAccount() + public async Task DeleteAccount() { - var header = HttpContext.Request.Headers.Authorization.FirstOrDefault() ?? - throw new UnauthorizedAccessException("Unauthorized"); - - userService.DeleteAccount(AuthDao.GetPrincipalEmail(User), header); + await userService.DeleteAccount(AuthDao.GetPrincipalEmail(User)); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Module/User/UserService.cs b/rag-2-backend/Infrastructure/Module/User/UserService.cs index 4327e9e..5eb7f88 100644 --- a/rag-2-backend/Infrastructure/Module/User/UserService.cs +++ b/rag-2-backend/Infrastructure/Module/User/UserService.cs @@ -21,9 +21,9 @@ public class UserService( RefreshTokenDao refreshTokenDao, CourseDao courseDao) { - public void RegisterUser(UserRequest userRequest) + public async Task RegisterUser(UserRequest userRequest) { - if (context.Users.Any(u => u.Email == userRequest.Email)) + if (await context.Users.AnyAsync(u => u.Email == userRequest.Email)) throw new BadRequestException("User already exists"); Database.Entity.User user = new(userRequest.Email) @@ -32,7 +32,7 @@ public void RegisterUser(UserRequest userRequest) Password = HashUtil.HashPassword(userRequest.Password) }; - UpdateUserProperties( + await UpdateUserProperties( userRequest.StudyCycleYearA, userRequest.StudyCycleYearB, userRequest.CourseId, @@ -40,16 +40,16 @@ public void RegisterUser(UserRequest userRequest) user ); - context.Users.Add(user); - GenerateAccountTokenAndSendConfirmationMail(user); - context.SaveChanges(); + await context.Users.AddAsync(user); + await GenerateAccountTokenAndSendConfirmationMail(user); + await context.SaveChangesAsync(); } - public void UpdateAccount(UserEditRequest request, string principalEmail) + public async Task UpdateAccount(UserEditRequest request, string principalEmail) { - var user = userDao.GetUserByEmailOrThrow(principalEmail); + var user = await userDao.GetUserByEmailOrThrow(principalEmail); - UpdateUserProperties( + await UpdateUserProperties( request.StudyCycleYearA, request.StudyCycleYearB, request.CourseId, @@ -59,65 +59,65 @@ public void UpdateAccount(UserEditRequest request, string principalEmail) user.Name = request.Name; context.Users.Update(user); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void ResendConfirmationEmail(string email) + public async Task ResendConfirmationEmail(string email) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); if (user.Confirmed) throw new BadRequestException("User is already confirmed"); context.AccountConfirmationTokens.RemoveRange( context.AccountConfirmationTokens.Where(a => a.User.Email == user.Email) ); - GenerateAccountTokenAndSendConfirmationMail(user); - context.SaveChanges(); + await GenerateAccountTokenAndSendConfirmationMail(user); + await context.SaveChangesAsync(); } - public void ConfirmAccount(string tokenValue) + public async Task ConfirmAccount(string tokenValue) { - var token = context.AccountConfirmationTokens + var token = await context.AccountConfirmationTokens .Include(t => t.User) - .SingleOrDefault(t => t.Token == tokenValue) + .SingleOrDefaultAsync(t => t.Token == tokenValue) ?? throw new BadRequestException("Invalid token"); if (token.Expiration < DateTime.Now) throw new BadRequestException("Invalid token"); token.User.Confirmed = true; context.AccountConfirmationTokens.Remove(token); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void RequestPasswordReset(string email) + public async Task RequestPasswordReset(string email) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); context.PasswordResetTokens.RemoveRange( context.PasswordResetTokens.Where(a => a.User.Email == user.Email) ); - GeneratePasswordResetTokenAndSendMail(user); - context.SaveChanges(); + await GeneratePasswordResetTokenAndSendMail(user); + await context.SaveChangesAsync(); } - public void ResetPassword(string tokenValue, string newPassword) + public async Task ResetPassword(string tokenValue, string newPassword) { - var token = context.PasswordResetTokens + var token = await context.PasswordResetTokens .Include(t => t.User) - .SingleOrDefault(t => t.Token == tokenValue) + .SingleOrDefaultAsync(t => t.Token == tokenValue) ?? throw new BadRequestException("Invalid token"); if (token.Expiration < DateTime.Now) throw new BadRequestException("Invalid token"); token.User.Password = HashUtil.HashPassword(newPassword); context.PasswordResetTokens.Remove(token); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void ChangePassword(string email, string oldPassword, string newPassword) + public async Task ChangePassword(string email, string oldPassword, string newPassword) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); if (!HashUtil.VerifyPassword(oldPassword, user.Password)) throw new BadRequestException("Invalid old password"); @@ -125,12 +125,12 @@ public void ChangePassword(string email, string oldPassword, string newPassword) throw new BadRequestException("Password cannot be same"); user.Password = HashUtil.HashPassword(newPassword); - context.SaveChanges(); + await context.SaveChangesAsync(); } - public void DeleteAccount(string email, string header) + public async Task DeleteAccount(string email) { - var user = userDao.GetUserByEmailOrThrow(email); + var user = await userDao.GetUserByEmailOrThrow(email); context.PasswordResetTokens.RemoveRange(context.PasswordResetTokens .Include(p => p.User) @@ -146,14 +146,13 @@ public void DeleteAccount(string email, string header) ); context.Users.Remove(user); - context.SaveChanges(); - - refreshTokenDao.RemoveTokensForUser(user); + await context.SaveChangesAsync(); + await refreshTokenDao.RemoveTokensForUser(user); } // - private void UpdateUserProperties( + private async Task UpdateUserProperties( int? studyCycleYearA, int? studyCycleYearB, int? courseId, string? group, Database.Entity.User user ) { @@ -172,7 +171,7 @@ private void UpdateUserProperties( user.StudyCycleYearA = studyCycleYearA ?? 0; user.StudyCycleYearB = studyCycleYearB ?? 0; - user.Course = courseId != null ? courseDao.GetCourseByIdOrThrow(courseId.Value) : null; + user.Course = courseId != null ? await courseDao.GetCourseByIdOrThrow(courseId.Value) : null; user.Group = group; } @@ -182,7 +181,7 @@ private static bool IsStudyCycleYearValid(int studyCycleYearA, int studyCycleYea studyCycleYearB - studyCycleYearA == 1; } - private void GenerateAccountTokenAndSendConfirmationMail(Database.Entity.User user) + private async Task GenerateAccountTokenAndSendConfirmationMail(Database.Entity.User user) { var token = new AccountConfirmationToken { @@ -190,11 +189,11 @@ private void GenerateAccountTokenAndSendConfirmationMail(Database.Entity.User us User = user, Expiration = DateTime.Now.AddDays(7) }; - context.AccountConfirmationTokens.Add(token); + await context.AccountConfirmationTokens.AddAsync(token); emailService.SendConfirmationEmail(user.Email, token.Token); } - private void GeneratePasswordResetTokenAndSendMail(Database.Entity.User user) + private async Task GeneratePasswordResetTokenAndSendMail(Database.Entity.User user) { var token = new PasswordResetToken { @@ -202,7 +201,7 @@ private void GeneratePasswordResetTokenAndSendMail(Database.Entity.User user) User = user, Expiration = DateTime.Now.AddDays(2) }; - context.PasswordResetTokens.Add(token); + await context.PasswordResetTokens.AddAsync(token); emailService.SendPasswordResetMail(user.Email, token.Token); } } \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Util/DataSortingUtil.cs b/rag-2-backend/Infrastructure/Util/DataSortingUtil.cs new file mode 100644 index 0000000..9fda330 --- /dev/null +++ b/rag-2-backend/Infrastructure/Util/DataSortingUtil.cs @@ -0,0 +1,17 @@ +#region + +using System.Linq.Expressions; +using rag_2_backend.Infrastructure.Common.Model; + +#endregion + +namespace rag_2_backend.Infrastructure.Util; + +public static class DataSortingUtil +{ + public static IQueryable ApplySorting(IQueryable query, Expression> keySelector, + SortDirection sortDirection) + { + return sortDirection == SortDirection.Desc ? query.OrderByDescending(keySelector) : query.OrderBy(keySelector); + } +} \ No newline at end of file diff --git a/rag-2-backend/Infrastructure/Util/HashUtil.cs b/rag-2-backend/Infrastructure/Util/HashUtil.cs index 0125d59..38435c6 100644 --- a/rag-2-backend/Infrastructure/Util/HashUtil.cs +++ b/rag-2-backend/Infrastructure/Util/HashUtil.cs @@ -1,6 +1,6 @@ namespace rag_2_backend.Infrastructure.Util; -public abstract class HashUtil +public static class HashUtil { public static string HashPassword(string password) { diff --git a/rag-2-backend/Infrastructure/Util/StatsUtil.cs b/rag-2-backend/Infrastructure/Util/StatsUtil.cs index 9e4fd6e..2ec11c6 100644 --- a/rag-2-backend/Infrastructure/Util/StatsUtil.cs +++ b/rag-2-backend/Infrastructure/Util/StatsUtil.cs @@ -20,16 +20,16 @@ GameRecordDao gameRecordDao { private readonly IDatabase _redisDatabase = redisConnection.GetDatabase(); - public GameStatsResponse UpdateCachedGameStats(Game game) + public async Task UpdateCachedGameStats(Game game) { - var records = gameRecordDao.GetGameRecordsByGameWithUser(game.Id); + var records = await 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) + TotalStorageMb = (await gameRecordDao.GetGameRecordsByGameWithUser(game.Id)) .Select(r => r.SizeMb) .ToList() .Sum(), @@ -45,14 +45,14 @@ public GameStatsResponse UpdateCachedGameStats(Game game) return gameStatsResponse; } - public OverallStatsResponse UpdateCachedStats() + public async Task UpdateCachedStats() { var overallStatsResponse = new OverallStatsResponse { - PlayersAmount = userDao.CountUsers(), - TotalMemoryMb = gameRecordDao.CountTotalStorageMb(), - GamesAmount = gameDao.GetAllGames().Count, - GameRecordsAmount = gameRecordDao.CountAllGameRecords(), + PlayersAmount = await userDao.CountUsers(), + TotalMemoryMb = await gameRecordDao.CountTotalStorageMb(), + GamesAmount = (await gameDao.GetAllGames()).Count, + GameRecordsAmount = await gameRecordDao.CountAllGameRecords(), StatsUpdatedDate = DateTime.Now }; diff --git a/rag-2-backend/Test/Dao/CourseDaoTest.cs b/rag-2-backend/Test/Dao/CourseDaoTest.cs index d5e65f8..bcc9dec 100644 --- a/rag-2-backend/Test/Dao/CourseDaoTest.cs +++ b/rag-2-backend/Test/Dao/CourseDaoTest.cs @@ -3,6 +3,7 @@ using HttpExceptions.Exceptions; using Microsoft.EntityFrameworkCore; using Moq; +using Moq.EntityFrameworkCore; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -27,35 +28,27 @@ public CourseDaoTest() private void SetUpCourses(IEnumerable courses) { - var coursesQueryable = courses.AsQueryable(); - var coursesDbSetMock = new Mock>(); - coursesDbSetMock.As>().Setup(m => m.Provider).Returns(coursesQueryable.Provider); - coursesDbSetMock.As>().Setup(m => m.Expression).Returns(coursesQueryable.Expression); - coursesDbSetMock.As>().Setup(m => m.ElementType).Returns(coursesQueryable.ElementType); - using var enumerator = coursesQueryable.GetEnumerator(); - coursesDbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(enumerator); - - _dbContextMock.Setup(db => db.Courses).Returns(coursesDbSetMock.Object); + _dbContextMock.Setup(db => db.Courses).ReturnsDbSet(courses); } [Fact] - public void GetCourseById_ShouldReturnCourse() + public async Task GetCourseById_ShouldReturnCourse() { var expectedCourse = new Course { Id = 1 }; SetUpCourses(new List { expectedCourse }); - var result = _courseDao.GetCourseByIdOrThrow(1); + var result = await _courseDao.GetCourseByIdOrThrow(1); Assert.Equal(expectedCourse, result); } [Fact] - public void GetCourseById_ShouldThrowNotFound() + public async Task GetCourseById_ShouldThrowNotFound() { SetUpCourses(new List()); - Assert.Throws(() => _courseDao.GetCourseByIdOrThrow(2)); + await Assert.ThrowsAsync(() => _courseDao.GetCourseByIdOrThrow(2)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Dao/GameDaoTest.cs b/rag-2-backend/Test/Dao/GameDaoTest.cs index c82988a..7e43799 100644 --- a/rag-2-backend/Test/Dao/GameDaoTest.cs +++ b/rag-2-backend/Test/Dao/GameDaoTest.cs @@ -3,6 +3,7 @@ using HttpExceptions.Exceptions; using Microsoft.EntityFrameworkCore; using Moq; +using Moq.EntityFrameworkCore; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -27,55 +28,47 @@ public GameDaoTests() private void SetUpGameDbSet(IEnumerable games) { - var gamesQueryable = games.AsQueryable(); - var gamesDbSetMock = new Mock>(); - gamesDbSetMock.As>().Setup(m => m.Provider).Returns(gamesQueryable.Provider); - gamesDbSetMock.As>().Setup(m => m.Expression).Returns(gamesQueryable.Expression); - gamesDbSetMock.As>().Setup(m => m.ElementType).Returns(gamesQueryable.ElementType); - using var enumerator = gamesQueryable.GetEnumerator(); - gamesDbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(enumerator); - - _dbContextMock.Setup(db => db.Games).Returns(gamesDbSetMock.Object); + _dbContextMock.Setup(db => db.Games).ReturnsDbSet(games); } [Fact] - public void GetGameByIdOrThrow_ShouldReturnGame_WhenGameExists() + public async Task GetGameByIdOrThrow_ShouldReturnGame_WhenGameExists() { const int gameId = 1; var expectedGame = new Game { Id = gameId, Name = "Test Game" }; SetUpGameDbSet(new List { expectedGame }); - var result = _gameDao.GetGameByIdOrThrow(gameId); + var result = await _gameDao.GetGameByIdOrThrow(gameId); Assert.Equal(expectedGame, result); } [Fact] - public void GetGameByIdOrThrow_ShouldThrowNotFoundException_WhenGameDoesNotExist() + public async Task GetGameByIdOrThrow_ShouldThrowNotFoundException_WhenGameDoesNotExist() { const int gameId = 1; SetUpGameDbSet(new List()); - Assert.Throws(() => _gameDao.GetGameByIdOrThrow(gameId)); + await Assert.ThrowsAsync(() => _gameDao.GetGameByIdOrThrow(gameId)); } [Fact] - public void GetGameByNameOrThrow_ShouldReturnGame_WhenGameWithMatchingNameExists() + public async Task GetGameByNameOrThrow_ShouldReturnGame_WhenGameWithMatchingNameExists() { const string gameName = "Test Game"; var expectedGame = new Game { Id = 1, Name = gameName }; SetUpGameDbSet(new List { expectedGame }); - var result = _gameDao.GetGameByNameOrThrow(gameName); + var result = await _gameDao.GetGameByNameOrThrow(gameName); Assert.Equal(expectedGame, result); } [Fact] - public void GetGameByNameOrThrow_ShouldThrowNotFoundException_WhenGameWithMatchingNameDoesNotExist() + public async Task GetGameByNameOrThrow_ShouldThrowNotFoundException_WhenGameWithMatchingNameDoesNotExist() { SetUpGameDbSet(new List()); - Assert.Throws(() => _gameDao.GetGameByNameOrThrow("g")); + await Assert.ThrowsAsync(() => _gameDao.GetGameByNameOrThrow("g")); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Dao/GameRecordDaoTest.cs b/rag-2-backend/Test/Dao/GameRecordDaoTest.cs index fc84a40..6dcd18b 100644 --- a/rag-2-backend/Test/Dao/GameRecordDaoTest.cs +++ b/rag-2-backend/Test/Dao/GameRecordDaoTest.cs @@ -3,9 +3,12 @@ using HttpExceptions.Exceptions; using Microsoft.EntityFrameworkCore; using Moq; +using Moq.EntityFrameworkCore; +using rag_2_backend.Infrastructure.Common.Model; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; +using rag_2_backend.Infrastructure.Module.GameRecord.Dto; using Xunit; #endregion @@ -27,20 +30,11 @@ public GameRecordDaoTests() private void SetUpGameRecordsDbSet(IEnumerable records) { - var recordsQueryable = records.AsQueryable(); - var gameRecordsDbSetMock = new Mock>(); - gameRecordsDbSetMock.As>().Setup(m => m.Provider).Returns(recordsQueryable.Provider); - gameRecordsDbSetMock.As>().Setup(m => m.Expression).Returns(recordsQueryable.Expression); - gameRecordsDbSetMock.As>().Setup(m => m.ElementType) - .Returns(recordsQueryable.ElementType); - using var enumerator = recordsQueryable.GetEnumerator(); - gameRecordsDbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(enumerator); - - _dbContextMock.Setup(db => db.GameRecords).Returns(gameRecordsDbSetMock.Object); + _dbContextMock.Setup(db => db.GameRecords).ReturnsDbSet(records); } [Fact] - public void GetRecordsByGameAndUser_ShouldReturnRecords_WhenRecordsExist() + public async Task GetRecordsByGameAndUser_ShouldReturnRecords_WhenRecordsExist() { const int gameId = 1; const string email = "test@example.com"; @@ -64,13 +58,20 @@ public void GetRecordsByGameAndUser_ShouldReturnRecords_WhenRecordsExist() }; SetUpGameRecordsDbSet(new List { gameRecord }); - var result = _gameRecordDao.GetRecordsByGameAndUser(gameId, 1); + var result = await _gameRecordDao.GetRecordsByGameAndUser( + gameId, + 1, + null, + null, + null, SortDirection.Asc, + GameRecordSortByFields.Id + ); Assert.Single(result); } [Fact] - public void GetGameRecordsByUserWithGame_ShouldReturnRecords_WhenRecordsExist() + public async Task GetGameRecordsByUserWithGame_ShouldReturnRecords_WhenRecordsExist() { const int userId = 1; var user = new User @@ -92,14 +93,14 @@ public void GetGameRecordsByUserWithGame_ShouldReturnRecords_WhenRecordsExist() }; SetUpGameRecordsDbSet(new List { gameRecord }); - var result = _gameRecordDao.GetGameRecordsByUserWithGame(userId); + var result = await _gameRecordDao.GetGameRecordsByUserWithGame(userId); Assert.Single(result); Assert.Equal(game.Id, result[0].Game.Id); } [Fact] - public void GetGameRecordsByGameWithUser_ShouldReturnRecords_WhenRecordsExist() + public async Task GetGameRecordsByGameWithUser_ShouldReturnRecords_WhenRecordsExist() { const int gameId = 1; var game = new Game @@ -121,14 +122,14 @@ public void GetGameRecordsByGameWithUser_ShouldReturnRecords_WhenRecordsExist() }; SetUpGameRecordsDbSet(new List { gameRecord }); - var result = _gameRecordDao.GetGameRecordsByGameWithUser(gameId); + var result = await _gameRecordDao.GetGameRecordsByGameWithUser(gameId); Assert.Single(result); Assert.Equal(user.Id, result[0].User.Id); } [Fact] - public void GetRecordedGameById_ShouldReturnGameRecord_WhenRecordExists() + public async Task GetRecordedGameById_ShouldReturnGameRecord_WhenRecordExists() { const int recordId = 1; var gameRecord = new GameRecord @@ -140,17 +141,17 @@ public void GetRecordedGameById_ShouldReturnGameRecord_WhenRecordExists() }; SetUpGameRecordsDbSet(new List { gameRecord }); - var result = _gameRecordDao.GetRecordedGameById(recordId); + var result = await _gameRecordDao.GetRecordedGameById(recordId); Assert.Equal(recordId, result.Id); } [Fact] - public void GetRecordedGameById_ShouldThrowNotFoundException_WhenRecordDoesNotExist() + public async Task GetRecordedGameById_ShouldThrowNotFoundException_WhenRecordDoesNotExist() { const int recordId = 1; SetUpGameRecordsDbSet(new List()); - Assert.Throws(() => _gameRecordDao.GetRecordedGameById(recordId)); + await Assert.ThrowsAsync(() => _gameRecordDao.GetRecordedGameById(recordId)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Dao/RefreshTokenDaoTest.cs b/rag-2-backend/Test/Dao/RefreshTokenDaoTest.cs index 8393945..38f6507 100644 --- a/rag-2-backend/Test/Dao/RefreshTokenDaoTest.cs +++ b/rag-2-backend/Test/Dao/RefreshTokenDaoTest.cs @@ -2,6 +2,7 @@ using Microsoft.EntityFrameworkCore; using Moq; +using Moq.EntityFrameworkCore; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -26,19 +27,11 @@ public RefreshTokenDaoTests() private void SetUpRefreshTokensDbSet(IEnumerable tokens) { - var tokensQueryable = tokens.AsQueryable(); - var tokensDbSetMock = new Mock>(); - tokensDbSetMock.As>().Setup(m => m.Provider).Returns(tokensQueryable.Provider); - tokensDbSetMock.As>().Setup(m => m.Expression).Returns(tokensQueryable.Expression); - tokensDbSetMock.As>().Setup(m => m.ElementType).Returns(tokensQueryable.ElementType); - using var enumerator = tokensQueryable.GetEnumerator(); - tokensDbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(enumerator); - - _dbContextMock.Setup(db => db.RefreshTokens).Returns(tokensDbSetMock.Object); + _dbContextMock.Setup(db => db.RefreshTokens).ReturnsDbSet(tokens); } [Fact] - public void RemoveTokensForUser_ShouldRemoveAllTokensForSpecifiedUser() + public async Task RemoveTokensForUser_ShouldRemoveAllTokensForSpecifiedUser() { const int userId = 1; var user = new User @@ -65,16 +58,16 @@ public void RemoveTokensForUser_ShouldRemoveAllTokensForSpecifiedUser() SetUpRefreshTokensDbSet(tokens); - _refreshTokenDao.RemoveTokensForUser(user); + await _refreshTokenDao.RemoveTokensForUser(user); _dbContextMock.Verify(db => db.RefreshTokens .RemoveRange(It.Is> (r => r.All(t => t.User.Id == userId))), Times.Once); - _dbContextMock.Verify(db => db.SaveChanges(), Times.Once); + _dbContextMock.Verify(db => db.SaveChangesAsync(CancellationToken.None), Times.Once); } [Fact] - public void RemoveTokensForUser_ShouldNotThrow_WhenNoTokensExistForUser() + public async Task RemoveTokensForUser_ShouldNotThrow_WhenNoTokensExistForUser() { var user = new User { @@ -84,11 +77,11 @@ public void RemoveTokensForUser_ShouldNotThrow_WhenNoTokensExistForUser() }; SetUpRefreshTokensDbSet(new List()); - var exception = Record.Exception(() => _refreshTokenDao.RemoveTokensForUser(user)); + var exception = await Record.ExceptionAsync(() => _refreshTokenDao.RemoveTokensForUser(user)); Assert.Null(exception); _dbContextMock.Verify(db => db.RefreshTokens .RemoveRange(It.IsAny>()), Times.Once); - _dbContextMock.Verify(db => db.SaveChanges(), Times.Once); + _dbContextMock.Verify(db => db.SaveChangesAsync(CancellationToken.None), Times.Once); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Dao/UserDaoTest.cs b/rag-2-backend/Test/Dao/UserDaoTest.cs index b81c3a8..a0c3f2b 100644 --- a/rag-2-backend/Test/Dao/UserDaoTest.cs +++ b/rag-2-backend/Test/Dao/UserDaoTest.cs @@ -3,6 +3,7 @@ using HttpExceptions.Exceptions; using Microsoft.EntityFrameworkCore; using Moq; +using Moq.EntityFrameworkCore; using rag_2_backend.Infrastructure.Dao; using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; @@ -27,19 +28,11 @@ public UserDaoTests() private void SetUpUsersDbSet(IEnumerable users) { - var usersQueryable = users.AsQueryable(); - var usersDbSetMock = new Mock>(); - usersDbSetMock.As>().Setup(m => m.Provider).Returns(usersQueryable.Provider); - usersDbSetMock.As>().Setup(m => m.Expression).Returns(usersQueryable.Expression); - usersDbSetMock.As>().Setup(m => m.ElementType).Returns(usersQueryable.ElementType); - using var enumerator = usersQueryable.GetEnumerator(); - usersDbSetMock.As>().Setup(m => m.GetEnumerator()).Returns(enumerator); - - _dbContextMock.Setup(db => db.Users).Returns(usersDbSetMock.Object); + _dbContextMock.Setup(db => db.Users).ReturnsDbSet(users); } [Fact] - public void GetUserByIdOrThrow_ShouldReturnUser_WhenUserExists() + public async Task GetUserByIdOrThrow_ShouldReturnUser_WhenUserExists() { const int userId = 1; var user = new User @@ -51,23 +44,23 @@ public void GetUserByIdOrThrow_ShouldReturnUser_WhenUserExists() }; SetUpUsersDbSet(new List { user }); - var result = _userDao.GetUserByIdOrThrow(userId); + var result = await _userDao.GetUserByIdOrThrow(userId); Assert.Equal(userId, result.Id); Assert.Equal("test@example.com", result.Email); } [Fact] - public void GetUserByIdOrThrow_ShouldThrowNotFoundException_WhenUserDoesNotExist() + public async Task GetUserByIdOrThrow_ShouldThrowNotFoundException_WhenUserDoesNotExist() { const int userId = 1; SetUpUsersDbSet(new List()); - Assert.Throws(() => _userDao.GetUserByIdOrThrow(userId)); + await Assert.ThrowsAsync(() => _userDao.GetUserByIdOrThrow(userId)); } [Fact] - public void GetUserByEmailOrThrow_ShouldReturnUser_WhenUserExists() + public async Task GetUserByEmailOrThrow_ShouldReturnUser_WhenUserExists() { const string email = "test@example.com"; var user = new User @@ -79,18 +72,18 @@ public void GetUserByEmailOrThrow_ShouldReturnUser_WhenUserExists() }; SetUpUsersDbSet(new List { user }); - var result = _userDao.GetUserByEmailOrThrow(email); + var result = await _userDao.GetUserByEmailOrThrow(email); Assert.Equal(email, result.Email); Assert.Equal(1, result.Id); } [Fact] - public void GetUserByEmailOrThrow_ShouldThrowNotFoundException_WhenUserDoesNotExist() + public async Task GetUserByEmailOrThrow_ShouldThrowNotFoundException_WhenUserDoesNotExist() { const string email = "test@example.com"; SetUpUsersDbSet(new List()); - Assert.Throws(() => _userDao.GetUserByEmailOrThrow(email)); + await Assert.ThrowsAsync(() => _userDao.GetUserByEmailOrThrow(email)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/AdministrationServiceTest.cs b/rag-2-backend/Test/Service/AdministrationServiceTest.cs index bf740af..db31634 100644 --- a/rag-2-backend/Test/Service/AdministrationServiceTest.cs +++ b/rag-2-backend/Test/Service/AdministrationServiceTest.cs @@ -10,6 +10,7 @@ using rag_2_backend.Infrastructure.Database; using rag_2_backend.Infrastructure.Database.Entity; using rag_2_backend.Infrastructure.Module.Administration; +using rag_2_backend.Infrastructure.Module.Administration.Dto; using rag_2_backend.Infrastructure.Module.User.Dto; using Xunit; @@ -52,37 +53,37 @@ public AdministrationServiceTest() new List { _admin, _user }.AsQueryable().BuildMockDbSet().Object ); _userMock = new Mock(_contextMock.Object); - _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).Returns(_user); - _userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).Returns(_admin); + _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).ReturnsAsync(_user); + _userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).ReturnsAsync(_admin); _administrationService = new AdministrationService(_contextMock.Object, _userMock.Object); } [Fact] - public void ShouldBanUser() + public async Task ShouldBanUser() { - _administrationService.ChangeBanStatus(2, true); + await _administrationService.ChangeBanStatus(2, true); Assert.True(_user.Banned); - _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).Returns(_admin); - Assert.Throws( + _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).ReturnsAsync(_admin); + await Assert.ThrowsAsync( () => _administrationService.ChangeBanStatus(1, false)); } [Fact] - public void ShouldChangeRole() + public async Task ShouldChangeRole() { - _administrationService.ChangeRole(2, Role.Student); + await _administrationService.ChangeRole(2, Role.Student); Assert.Equal(Role.Student, _user.Role); - _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).Returns(_admin); - Assert.Throws( + _userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).ReturnsAsync(_admin); + await Assert.ThrowsAsync( () => _administrationService.ChangeRole(1, Role.Teacher)); } [Fact] - public void ShouldGetUserDetails() + public async Task ShouldGetUserDetails() { var response = new UserResponse { @@ -95,14 +96,15 @@ public void ShouldGetUserDetails() }; Assert.Equal(JsonConvert.SerializeObject(response), - JsonConvert.SerializeObject(_administrationService.GetUserDetails("email@prz.edu.pl", 1))); + JsonConvert.SerializeObject(await _administrationService.GetUserDetails("email@prz.edu.pl", 1))); - _userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).Returns(_user); - Assert.Throws(() => _administrationService.GetUserDetails("email1@prz.edu.pl", 1)); + _userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).ReturnsAsync(_user); + await Assert.ThrowsAsync( + () => _administrationService.GetUserDetails("email1@prz.edu.pl", 1)); } [Fact] - public void ShouldGetStudents() + public async Task ShouldGetStudents() { var response = new UserResponse { @@ -116,7 +118,9 @@ public void ShouldGetStudents() Assert.Equal( JsonConvert.SerializeObject(response), - JsonConvert.SerializeObject(_administrationService.GetStudents()[0]) + JsonConvert.SerializeObject((await _administrationService.GetUsers(Role.Admin, null, null, null, null, null, + SortDirection.Asc, + UserSortByFields.Id))[0]) ); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/AuthServiceTest.cs b/rag-2-backend/Test/Service/AuthServiceTest.cs index 9d26cd3..f1304a7 100644 --- a/rag-2-backend/Test/Service/AuthServiceTest.cs +++ b/rag-2-backend/Test/Service/AuthServiceTest.cs @@ -59,8 +59,8 @@ public AuthServiceTest() Mock userMock = new(_contextMock.Object); Mock refreshTokenDaoMock = new(_contextMock.Object); - userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).Returns(_user); - userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).Returns(_user); + userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).ReturnsAsync(_user); + userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).ReturnsAsync(_user); _authService = new AuthService(userMock.Object, refreshTokenDaoMock.Object, _contextMock.Object, _jwtUtilMock.Object); @@ -82,29 +82,29 @@ public AuthServiceTest() } [Fact] - public void ShouldLoginUser() + public async Task ShouldLoginUser() { - Assert.Throws( + await Assert.ThrowsAsync( () => _authService.LoginUser("email@prz.edu.pl", "pass", 30) ); //wrong password - Assert.Throws( + await Assert.ThrowsAsync( () => _authService.LoginUser("email@prz.edu.pl", "password", 30) ); //not confirmed _user.Confirmed = true; - _authService.LoginUser("email@prz.edu.pl", "password", 30); + await _authService.LoginUser("email@prz.edu.pl", "password", 30); _jwtUtilMock.Verify(j => j.GenerateJwt(It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public void ShouldLogoutUser() + public async Task ShouldLogoutUser() { - _authService.LogoutUser("Bearer header"); + await _authService.LogoutUser("Bearer header"); } [Fact] - public void ShouldGetMe() + public async Task ShouldGetMe() { var userResponse = new UserResponse { @@ -117,6 +117,6 @@ public void ShouldGetMe() }; Assert.Equal(JsonConvert.SerializeObject(userResponse), - JsonConvert.SerializeObject(_authService.GetMe("email@prz.edu.pl"))); + JsonConvert.SerializeObject(await _authService.GetMe("email@prz.edu.pl"))); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/CourseServiceTest.cs b/rag-2-backend/Test/Service/CourseServiceTest.cs index d8952f1..f1c6001 100644 --- a/rag-2-backend/Test/Service/CourseServiceTest.cs +++ b/rag-2-backend/Test/Service/CourseServiceTest.cs @@ -35,47 +35,47 @@ public CourseServiceTest() { Mock courseDaoMock = new(_contextMock.Object); _courseService = new CourseService(_contextMock.Object, courseDaoMock.Object); - courseDaoMock.Setup(dao => dao.GetCourseByIdOrThrow(It.IsAny())).Returns(_courses[0]); + courseDaoMock.Setup(dao => dao.GetCourseByIdOrThrow(It.IsAny())).ReturnsAsync(_courses[0]); _contextMock.Setup(c => c.Courses).Returns(_courses.AsQueryable().BuildMockDbSet().Object); } [Fact] - public void ShouldGetAllCourses() + public async Task ShouldGetAllCourses() { - var actualCourses = _courseService.GetCourses().Result; + var actualCourses = await _courseService.GetCourses(); Assert.Equal(JsonConvert.SerializeObject(_courses.Select(CourseMapper.Map)), JsonConvert.SerializeObject(actualCourses)); } [Fact] - public void ShouldAddCourse() + public async Task ShouldAddCourse() { var courseRequest = new CourseRequest { Name = "Course3" }; - _courseService.AddCourse(courseRequest); + await _courseService.AddCourse(courseRequest); _contextMock.Verify( - c => c.Courses.Add(It.Is(course => course.Name == courseRequest.Name)), + c => c.Courses.AddAsync(It.Is(course => course.Name == courseRequest.Name), CancellationToken.None), Times.Once); } [Fact] - public void ShouldNotAddCourseIfCourseAlreadyExists() + public async Task ShouldNotAddCourseIfCourseAlreadyExists() { var courseRequest = new CourseRequest { Name = "Course1" }; - Assert.Throws(() => _courseService.AddCourse(courseRequest)); + await Assert.ThrowsAsync(() => _courseService.AddCourse(courseRequest)); } [Fact] - public void ShouldRemoveCourse() + public async Task ShouldRemoveCourse() { var users = new List { @@ -89,13 +89,13 @@ public void ShouldRemoveCourse() }; _contextMock.Setup(c => c.Users).Returns(users.AsQueryable().BuildMockDbSet().Object); - _courseService.RemoveCourse(1); + await _courseService.RemoveCourse(1); _contextMock.Verify(c => c.Courses.Remove(It.Is(course => course.Id == 1)), Times.Once); } [Fact] - public void ShouldThrowWhenRemoveCourse() + public async Task ShouldThrowWhenRemoveCourse() { var users = new List { @@ -109,18 +109,18 @@ public void ShouldThrowWhenRemoveCourse() }; _contextMock.Setup(c => c.Users).Returns(users.AsQueryable().BuildMockDbSet().Object); - Assert.Throws(() => _courseService.RemoveCourse(1)); + await Assert.ThrowsAsync(() => _courseService.RemoveCourse(1)); } [Fact] - public void ShouldUpdateCourse() + public async Task ShouldUpdateCourse() { var courseRequest = new CourseRequest { Name = "Course3" }; - _courseService.EditCourse(courseRequest, 1); + await _courseService.EditCourse(courseRequest, 1); _contextMock.Verify( c => c.Courses.Update(It.Is(course => course.Name == courseRequest.Name)), @@ -128,13 +128,13 @@ public void ShouldUpdateCourse() } [Fact] - public void ShouldNotUpdateCourseIfCourseAlreadyExists() + public async Task ShouldNotUpdateCourseIfCourseAlreadyExists() { var courseRequest = new CourseRequest { Name = "Course2" }; - Assert.Throws(() => _courseService.EditCourse(courseRequest, 1)); + await Assert.ThrowsAsync(() => _courseService.EditCourse(courseRequest, 1)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/GameRecordServiceTest.cs b/rag-2-backend/Test/Service/GameRecordServiceTest.cs index ee9cddb..f19478a 100644 --- a/rag-2-backend/Test/Service/GameRecordServiceTest.cs +++ b/rag-2-backend/Test/Service/GameRecordServiceTest.cs @@ -47,7 +47,7 @@ public GameRecordServiceTests() } [Fact] - public void GetRecordsByGameAndUser_ShouldReturnGameRecords() + public async Task GetRecordsByGameAndUser_ShouldReturnGameRecords() { const int gameId = 1; const string email = "test@example.com"; @@ -74,16 +74,31 @@ public void GetRecordsByGameAndUser_ShouldReturnGameRecords() Password = null!, Name = null! }; - _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).Returns(user); - _gameRecordDaoMock.Setup(dao => dao.GetRecordsByGameAndUser(gameId, 1)).Returns(records); - - var result = _gameRecordService.GetRecordsByGameAndUser(gameId, 1, email); + _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).ReturnsAsync(user); + _gameRecordDaoMock.Setup(dao => dao.GetRecordsByGameAndUser( + gameId, + 1, + null, + null, + null, SortDirection.Asc, + GameRecordSortByFields.Id + )).ReturnsAsync(records); + + var result = await _gameRecordService.GetRecordsByGameAndUser( + gameId, + 1, + null, + null, + null, SortDirection.Asc, + GameRecordSortByFields.Id, + email + ); Assert.Equal(records, result); } [Fact] - public void DownloadRecordData_ShouldReturnSerializedRecord() + public async Task DownloadRecordData_ShouldReturnSerializedRecord() { const int recordedGameId = 1; const string email = "test@example.com"; @@ -106,16 +121,16 @@ public void DownloadRecordData_ShouldReturnSerializedRecord() Values = [] }; - _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).Returns(user); - _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(recordedGameId)).Returns(recordedGame); + _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).ReturnsAsync(user); + _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(recordedGameId)).ReturnsAsync(recordedGame); - var result = _gameRecordService.DownloadRecordData(recordedGameId, email); + var result = await _gameRecordService.DownloadRecordData(recordedGameId, email); Assert.Equal(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(GameRecordMapper.JsonMap(recordedGame))), result); } [Fact] - public void DownloadRecordData_ShouldThrowBadRequestException_WhenPermissionDenied() + public async Task DownloadRecordData_ShouldThrowBadRequestException_WhenPermissionDenied() { const int recordedGameId = 1; const string email = "test@example.com"; @@ -140,14 +155,15 @@ public void DownloadRecordData_ShouldThrowBadRequestException_WhenPermissionDeni Values = null! }; - _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).Returns(user); - _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(recordedGameId)).Returns(recordedGame); + _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).ReturnsAsync(user); + _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(recordedGameId)).ReturnsAsync(recordedGame); - Assert.Throws(() => _gameRecordService.DownloadRecordData(recordedGameId, email)); + await Assert.ThrowsAsync(() => + _gameRecordService.DownloadRecordData(recordedGameId, email)); } [Fact] - public void RemoveGameRecord_ShouldRemoveRecord_WhenUserHasPermission() + public async Task RemoveGameRecord_ShouldRemoveRecord_WhenUserHasPermission() { const int gameRecordId = 1; const string email = "test@example.com"; @@ -168,13 +184,13 @@ public void RemoveGameRecord_ShouldRemoveRecord_WhenUserHasPermission() _dbContextMock.Setup(ctx => ctx.GameRecords).Returns(() => new List { gameRecord } .AsQueryable().BuildMockDbSet().Object); - _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).Returns(user); - _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(gameRecordId)).Returns(gameRecord); + _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).ReturnsAsync(user); + _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(gameRecordId)).ReturnsAsync(gameRecord); - _gameRecordService.RemoveGameRecord(gameRecordId, email); + await _gameRecordService.RemoveGameRecord(gameRecordId, email); _dbContextMock.Verify(db => db.GameRecords.Remove(gameRecord), Times.Once); - _dbContextMock.Verify(db => db.SaveChanges(), Times.Once); + _dbContextMock.Verify(db => db.SaveChangesAsync(CancellationToken.None), Times.Once); } [Fact] @@ -202,9 +218,9 @@ public void RemoveGameRecord_ShouldThrowBadRequestException_WhenPermissionDenied Values = null! }; - _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).Returns(user); - _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(gameRecordId)).Returns(gameRecord); + _userDaoMock.Setup(dao => dao.GetUserByEmailOrThrow(email)).ReturnsAsync(user); + _gameRecordDaoMock.Setup(dao => dao.GetRecordedGameById(gameRecordId)).ReturnsAsync(gameRecord); - Assert.Throws(() => _gameRecordService.RemoveGameRecord(gameRecordId, email)); + Assert.ThrowsAsync(() => _gameRecordService.RemoveGameRecord(gameRecordId, email)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/GameServiceTest.cs b/rag-2-backend/Test/Service/GameServiceTest.cs index a121ade..a9cf9cc 100644 --- a/rag-2-backend/Test/Service/GameServiceTest.cs +++ b/rag-2-backend/Test/Service/GameServiceTest.cs @@ -35,64 +35,64 @@ public GameServiceTest() { Mock gameDaoMock = new(_contextMock.Object); _gameService = new GameService(_contextMock.Object, gameDaoMock.Object); - gameDaoMock.Setup(dao => dao.GetGameByIdOrThrow(It.IsAny())).Returns(_games[0]); + gameDaoMock.Setup(dao => dao.GetGameByIdOrThrow(It.IsAny())).ReturnsAsync(_games[0]); _contextMock.Setup(c => c.Games).Returns(_games.AsQueryable().BuildMockDbSet().Object); _contextMock.Setup(c => c.GameRecords) .Returns(new List().AsQueryable().BuildMockDbSet().Object); } [Fact] - public void ShouldGetAllGames() + public async Task ShouldGetAllGames() { - var actualGames = _gameService.GetGames().Result; + var actualGames = await _gameService.GetGames(); Assert.Equal(JsonConvert.SerializeObject(_games.Select(GameMapper.Map)), JsonConvert.SerializeObject(actualGames)); } [Fact] - public void ShouldAddGame() + public async Task ShouldAddGame() { var gameRequest = new GameRequest { Name = "Game3" }; - _gameService.AddGame(gameRequest); + await _gameService.AddGame(gameRequest); _contextMock.Verify( - c => c.Games.Add(It.Is(g => g.Name == gameRequest.Name)), + c => c.Games.AddAsync(It.Is(g => g.Name == gameRequest.Name), CancellationToken.None), Times.Once); } [Fact] - public void ShouldNotAddGameIfGameAlreadyExists() + public async Task ShouldNotAddGameIfGameAlreadyExists() { var gameRequest = new GameRequest { Name = "Game1" }; - Assert.Throws(() => _gameService.AddGame(gameRequest)); + await Assert.ThrowsAsync(() => _gameService.AddGame(gameRequest)); } [Fact] - public void ShouldRemoveGame() + public async Task ShouldRemoveGame() { - _gameService.RemoveGame(1); + await _gameService.RemoveGame(1); _contextMock.Verify(c => c.Games.Remove(It.Is(g => g.Id == 1)), Times.Once); } [Fact] - public void ShouldUpdateGame() + public async Task ShouldUpdateGame() { var gameRequest = new GameRequest { Name = "Game3" }; - _gameService.EditGame(gameRequest, 1); + await _gameService.EditGame(gameRequest, 1); _contextMock.Verify( c => c.Games.Update(It.Is(g => g.Name == gameRequest.Name)), @@ -100,13 +100,13 @@ public void ShouldUpdateGame() } [Fact] - public void ShouldNotUpdateGameIfGameAlreadyExists() + public async Task ShouldNotUpdateGameIfGameAlreadyExists() { var gameRequest = new GameRequest { Name = "Game2" }; - Assert.Throws(() => _gameService.EditGame(gameRequest, 1)); + await Assert.ThrowsAsync(() => _gameService.EditGame(gameRequest, 1)); } } \ No newline at end of file diff --git a/rag-2-backend/Test/Service/StatsServiceTest.cs b/rag-2-backend/Test/Service/StatsServiceTest.cs index bbb9995..812c3bc 100644 --- a/rag-2-backend/Test/Service/StatsServiceTest.cs +++ b/rag-2-backend/Test/Service/StatsServiceTest.cs @@ -57,7 +57,7 @@ public StatsServiceTests() } [Fact] - public void GetStatsForUser_ShouldReturnCorrectStats_WhenUserHasPermission() + public async Task GetStatsForUser_ShouldReturnCorrectStats_WhenUserHasPermission() { const string userEmail = "user@example.com"; const int userId = 123; @@ -112,11 +112,11 @@ public void GetStatsForUser_ShouldReturnCorrectStats_WhenUserHasPermission() } }; - _mockUserDao.Setup(u => u.GetUserByEmailOrThrow(userEmail)).Returns(principal); - _mockUserDao.Setup(u => u.GetUserByIdOrThrow(userId)).Returns(user); - _mockGameRecordDao.Setup(gr => gr.GetGameRecordsByUserWithGame(userId)).Returns(records); + _mockUserDao.Setup(u => u.GetUserByEmailOrThrow(userEmail)).ReturnsAsync(principal); + _mockUserDao.Setup(u => u.GetUserByIdOrThrow(userId)).ReturnsAsync(user); + _mockGameRecordDao.Setup(gr => gr.GetGameRecordsByUserWithGame(userId)).ReturnsAsync(records); - var result = _statsService.GetStatsForUser(userEmail, userId); + var result = await _statsService.GetStatsForUser(userEmail, userId); Assert.Equal(1, result.Games); Assert.Equal(2, result.Plays); @@ -124,7 +124,7 @@ public void GetStatsForUser_ShouldReturnCorrectStats_WhenUserHasPermission() } [Fact] - public void GetStatsForGame_ShouldReturnCachedStats_WhenDataIsInCache() + public async Task GetStatsForGame_ShouldReturnCachedStats_WhenDataIsInCache() { const int gameId = 1; var game = new Game { Id = gameId, Name = "Game 1" }; @@ -139,18 +139,18 @@ public void GetStatsForGame_ShouldReturnCachedStats_WhenDataIsInCache() StatsUpdatedDate = DateTime.Now }; - _mockGameDao.Setup(g => g.GetGameByIdOrThrow(gameId)).Returns(game); + _mockGameDao.Setup(g => g.GetGameByIdOrThrow(gameId)).ReturnsAsync(game); _mockGameRecordDao.Setup(dao => dao.GetGameRecordsByGameWithUser(It.IsAny())) - .Returns([]); + .ReturnsAsync([]); - var result = _statsService.GetStatsForGame(gameId); + var result = await _statsService.GetStatsForGame(gameId); Assert.Equal(cachedStats.Plays, result.Plays); Assert.Equal(cachedStats.TotalStorageMb, result.TotalStorageMb); } [Fact] - public void GetStatsForGame_ShouldUpdateCache_WhenDataIsExpired() + public async Task GetStatsForGame_ShouldUpdateCache_WhenDataIsExpired() { const int gameId = 1; var game = new Game { Id = gameId, Name = "Game 1" }; @@ -202,10 +202,10 @@ public void GetStatsForGame_ShouldUpdateCache_WhenDataIsExpired() StatsUpdatedDate = DateTime.Now }; - _mockGameRecordDao.Setup(gr => gr.GetGameRecordsByGameWithUser(gameId)).Returns(records); - _mockGameDao.Setup(g => g.GetGameByIdOrThrow(gameId)).Returns(game); + _mockGameRecordDao.Setup(gr => gr.GetGameRecordsByGameWithUser(gameId)).ReturnsAsync(records); + _mockGameDao.Setup(g => g.GetGameByIdOrThrow(gameId)).ReturnsAsync(game); - var result = _statsService.GetStatsForGame(gameId); + var result = await _statsService.GetStatsForGame(gameId); Assert.Equal(gameStatsResponse.Plays, result.Plays); Assert.Equal(gameStatsResponse.TotalStorageMb, result.TotalStorageMb); diff --git a/rag-2-backend/Test/Service/UserServiceTest.cs b/rag-2-backend/Test/Service/UserServiceTest.cs index db9fdd3..17e700e 100644 --- a/rag-2-backend/Test/Service/UserServiceTest.cs +++ b/rag-2-backend/Test/Service/UserServiceTest.cs @@ -62,13 +62,13 @@ public UserServiceTest() Mock userMock = new(_contextMock.Object); Mock refreshTokenDaoMock = new(_contextMock.Object); Mock courseDaoMock = new(_contextMock.Object); - userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).Returns(_user); - userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).Returns(_user); + userMock.Setup(u => u.GetUserByIdOrThrow(It.IsAny())).ReturnsAsync(_user); + userMock.Setup(u => u.GetUserByEmailOrThrow(It.IsAny())).ReturnsAsync(_user); _userService = new UserService(_contextMock.Object, _emailService.Object, userMock.Object, refreshTokenDaoMock.Object, courseDaoMock.Object); - courseDaoMock.Setup(c => c.GetCourseByIdOrThrow(It.IsAny())).Returns(_user.Course); + courseDaoMock.Setup(c => c.GetCourseByIdOrThrow(It.IsAny()))!.ReturnsAsync(_user.Course); _contextMock.Setup(c => c.Users).Returns(() => new List { _user } .AsQueryable().BuildMockDbSet().Object); _contextMock.Setup(c => c.AccountConfirmationTokens) @@ -85,20 +85,22 @@ public UserServiceTest() } [Fact] - public void ShouldRegisterUser() + public async Task ShouldRegisterUser() { - _userService.RegisterUser(new UserRequest + await _userService.RegisterUser(new UserRequest { Email = "email1@prz.edu.pl", Password = "pass", StudyCycleYearA = 2022, StudyCycleYearB = 2023, Name = "John" } ); - _contextMock.Verify(c => c.Users.Add(It.IsAny()), Times.Once); - _contextMock.Verify(c => c.AccountConfirmationTokens.Add(It.IsAny()), Times.Once); + _contextMock.Verify(c => c.Users.AddAsync(It.IsAny(), CancellationToken.None), Times.Once); + _contextMock.Verify( + c => c.AccountConfirmationTokens.AddAsync(It.IsAny(), CancellationToken.None), + Times.Once); _emailService.Verify(e => e.SendConfirmationEmail(It.IsAny(), It.IsAny()), Times.Once); - Assert.Throws( + await Assert.ThrowsAsync( () => _userService.RegisterUser(new UserRequest { Email = "email1@stud.prz.edu.pl", Password = "pass", StudyCycleYearA = 2020, StudyCycleYearB = 2023, @@ -109,61 +111,66 @@ public void ShouldRegisterUser() } [Fact] - public void ShouldResendConfirmationMail() + public async Task ShouldResendConfirmationMail() { - _userService.ResendConfirmationEmail("email@prz.edu.pl"); + await _userService.ResendConfirmationEmail("email@prz.edu.pl"); - _contextMock.Verify(c => c.AccountConfirmationTokens.Add(It.IsAny()), Times.Once); + _contextMock.Verify( + c => c.AccountConfirmationTokens.AddAsync(It.IsAny(), CancellationToken.None), + Times.Once); _emailService.Verify(e => e.SendConfirmationEmail(It.IsAny(), It.IsAny()), Times.Once); _user.Confirmed = true; - Assert.Throws(() => _userService.ResendConfirmationEmail("email@prz.edu.pl")); + await Assert.ThrowsAsync(() => _userService.ResendConfirmationEmail("email@prz.edu.pl")); } [Fact] - public void ShouldConfirmAccount() + public async Task ShouldConfirmAccount() { - _userService.ConfirmAccount(_accountToken.Token); + await _userService.ConfirmAccount(_accountToken.Token); _contextMock.Verify(c => c.AccountConfirmationTokens.Remove(It.IsAny()), Times.Once); - Assert.Throws(() => _userService.ConfirmAccount("token")); //wrong token + await Assert.ThrowsAsync(() => _userService.ConfirmAccount("token")); //wrong token _accountToken.Expiration = DateTime.Now.AddDays(-7); - Assert.Throws(() => _userService.ConfirmAccount(_accountToken.Token)); //invalid date + await Assert.ThrowsAsync(() => + _userService.ConfirmAccount(_accountToken.Token)); //invalid date } [Fact] - public void ShouldRequestPasswordReset() + public async Task ShouldRequestPasswordReset() { - _userService.RequestPasswordReset("email@prz.edu.pl"); + await _userService.RequestPasswordReset("email@prz.edu.pl"); - _contextMock.Verify(c => c.PasswordResetTokens.Add(It.IsAny()), Times.Once); + _contextMock.Verify(c => c.PasswordResetTokens.AddAsync(It.IsAny(), CancellationToken.None), + Times.Once); _emailService.Verify(e => e.SendPasswordResetMail(It.IsAny(), It.IsAny()), Times.Once); } [Fact] - public void ShouldResetPassword() + public async Task ShouldResetPassword() { - _userService.ResetPassword(_passwordToken.Token, "pass"); + await _userService.ResetPassword(_passwordToken.Token, "pass"); _contextMock.Verify(c => c.PasswordResetTokens.Remove(It.IsAny()), Times.Once); - Assert.Throws(() => _userService.ResetPassword("token1", "pass")); //wrong token + await Assert.ThrowsAsync(() => _userService.ResetPassword("token1", "pass")); //wrong token _passwordToken.Expiration = DateTime.Now.AddDays(-7); - Assert.Throws(() => + await Assert.ThrowsAsync(() => _userService.ResetPassword(_passwordToken.Token, "pass")); //invalid date } [Fact] - public void ShouldChangePassword() + public async Task ShouldChangePassword() { - _userService.ChangePassword("email@prz.edu.pl", "password", "pass2"); + await _userService.ChangePassword("email@prz.edu.pl", "password", "pass2"); - Assert.Throws(() => _userService.ChangePassword("email@prz.edu.pl", "pa4ss2", "pas2")); + await Assert.ThrowsAsync(() => + _userService.ChangePassword("email@prz.edu.pl", "pa4ss2", "pas2")); } [Fact] - public void ShouldDeleteAccount() + public async Task ShouldDeleteAccount() { - _userService.DeleteAccount("email@prz.edu.pl", "Bearer header"); + await _userService.DeleteAccount("email@prz.edu.pl"); _contextMock.Verify(c => c.Users.Remove(It.IsAny()), Times.Once); } diff --git a/rag-2-backend/Test/Util/StatsUtilTest.cs b/rag-2-backend/Test/Util/StatsUtilTest.cs index 3024c39..b34701d 100644 --- a/rag-2-backend/Test/Util/StatsUtilTest.cs +++ b/rag-2-backend/Test/Util/StatsUtilTest.cs @@ -45,7 +45,7 @@ public StatsUtilTests() } [Fact] - public void UpdateCachedGameStats_ShouldCacheAndReturnGameStats() + public async Task UpdateCachedGameStats_ShouldCacheAndReturnGameStats() { var game = new Game { @@ -84,11 +84,11 @@ public void UpdateCachedGameStats_ShouldCacheAndReturnGameStats() } }; - _mockGameRecordDao.Setup(dao => dao.GetGameRecordsByGameWithUser(game.Id)).Returns(gameRecords); - _mockGameRecordDao.Setup(dao => dao.CountAllGameRecords()).Returns(2); - _mockGameDao.Setup(dao => dao.GetAllGames()).Returns([game]); + _mockGameRecordDao.Setup(dao => dao.GetGameRecordsByGameWithUser(game.Id)).ReturnsAsync(gameRecords); + _mockGameRecordDao.Setup(dao => dao.CountAllGameRecords()).ReturnsAsync(2); + _mockGameDao.Setup(dao => dao.GetAllGames()).ReturnsAsync([game]); - var result = _statsUtil.UpdateCachedGameStats(game); + var result = await _statsUtil.UpdateCachedGameStats(game); Assert.Equal(gameRecords[0].Started, result.FirstPlayed); Assert.Equal(gameRecords[1].Ended, result.LastPlayed); @@ -99,13 +99,13 @@ public void UpdateCachedGameStats_ShouldCacheAndReturnGameStats() } [Fact] - public void UpdateCachedStats_ShouldCacheAndReturnOverallStats() + public async Task UpdateCachedStats_ShouldCacheAndReturnOverallStats() { - _mockUserDao.Setup(dao => dao.CountUsers()).Returns(100); - _mockGameRecordDao.Setup(dao => dao.CountTotalStorageMb()).Returns(500); - _mockGameDao.Setup(dao => dao.GetAllGames()).Returns([]); + _mockUserDao.Setup(dao => dao.CountUsers()).ReturnsAsync(100); + _mockGameRecordDao.Setup(dao => dao.CountTotalStorageMb()).ReturnsAsync(500); + _mockGameDao.Setup(dao => dao.GetAllGames()).ReturnsAsync([]); - var result = _statsUtil.UpdateCachedStats(); + var result = await _statsUtil.UpdateCachedStats(); Assert.Equal(100, result.PlayersAmount); Assert.Equal(500, result.TotalMemoryMb); diff --git a/rag-2-backend/appsettings.Development.json b/rag-2-backend/appsettings.Development.json index 0df88ac..8259ad1 100644 --- a/rag-2-backend/appsettings.Development.json +++ b/rag-2-backend/appsettings.Development.json @@ -7,7 +7,8 @@ }, "AllowedOrigins": "http://localhost:4200,http://localhost", "Jwt": { - "Key": "MIHcAgEBBEIBIHk9RDBDHoudslGHKpNqh8Tsr7fWu0J" + "Key": "MIHcAgEBBEIBIHk9RDBDHoudslGHKpNqh8Tsr7fWu0J", + "ExpireMinutes": 100 }, "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Database=postgres;Username=postgres;Password=postgres" diff --git a/rag-2-backend/rag-2-backend.csproj b/rag-2-backend/rag-2-backend.csproj index e080c49..681b0e0 100644 --- a/rag-2-backend/rag-2-backend.csproj +++ b/rag-2-backend/rag-2-backend.csproj @@ -21,6 +21,7 @@ +