Skip to content

Commit

Permalink
Merge pull request #51 from BinaryStudioAcademy/task/48-combine-main-…
Browse files Browse the repository at this point in the history
…page

Feature/48 -Combine main page with backend
  • Loading branch information
tatianahutii committed Sep 5, 2023
2 parents bff31a2 + 863ff47 commit 17e6800
Show file tree
Hide file tree
Showing 55 changed files with 23,592 additions and 1,474 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ namespace LeetWars.Core.BLL.Interfaces
{
public interface IChallengeService
{
Task<ICollection<ChallengePreviewDto>> GetChallengesAsync(ChallengesFiltersDto filters);

Task<ICollection<ChallengePreviewDto>> GetChallengesAsync(ChallengesFiltersDto filters, PageSettingsDto? page);
Task<ChallengePreviewDto> GetChallengeSuggestionAsync(SuggestionSettingsDto settings);
Task<ChallengeFullDto> GetChallengeByIdAsync(long id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace LeetWars.Core.BLL.Interfaces
{
public interface IUserIdGetter
{
/// <summary>
/// Returns current userId or 0 if no userId is presented
/// </summary>
string CurrentUserId { get; }

/// <summary>
/// Throws exception if not userId is presented
/// </summary>
/// <returns></returns>
string GetCurrentUserIdOrThrow();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace LeetWars.Core.BLL.Interfaces
{
public interface IUserIdSetter
{
void SetUserId(string userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public ChallengeProfile()
{
CreateMap<Challenge, ChallengePreviewDto>()
.ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author != null ? $"{src.Author.FirstName} {src.Author.LastName}" : null))
.ForMember(dest => dest.LevelName, opt => opt.MapFrom(src => src.Level != null ? src.Level.Name : null))
.ForMember(dest => dest.Tags, opt => opt.MapFrom(src => src.Tags))
.ForMember(dest => dest.Title, opt => opt.MapFrom(src => src.Title))
.ForMember(dest => dest.Languages, opt => opt.MapFrom(src => src.Versions.Select(version => version.Language)))
Expand Down
157 changes: 131 additions & 26 deletions backend/LeetWars.Core/LeetWars.Core.BLL/Services/ChallengeService.cs
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
using AutoMapper;
using System.Security.Cryptography;
using AutoMapper;
using LeetWars.Core.BLL.Interfaces;
using LeetWars.Core.Common.DTO.Challenge;
using LeetWars.Core.Common.DTO.Filters;
using LeetWars.Core.DAL.Context;
using LeetWars.Core.DAL.Entities;
using LeetWars.Core.DAL.Enums;
using LeetWars.Core.DAL.Extensions;
using Microsoft.EntityFrameworkCore;

namespace LeetWars.Core.BLL.Services
{
public class ChallengeService : BaseService, IChallengeService
{
public ChallengeService(LeetWarsCoreContext context, IMapper mapper) : base(context, mapper) { }
private readonly IUserIdGetter _userIdGetter;

public async Task<ICollection<ChallengePreviewDto>> GetChallengesAsync(ChallengesFiltersDto filters)
public ChallengeService(
LeetWarsCoreContext context,
IMapper mapper,
IUserIdGetter userIdGetter
) : base(context, mapper)
{
_userIdGetter = userIdGetter;
}

public async Task<ICollection<ChallengePreviewDto>> GetChallengesAsync(ChallengesFiltersDto filters,
PageSettingsDto? page)
{
var challenges = _context.Challenges
.Include(challenge => challenge.Tags)
.Include(challenge => challenge.Author)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Language)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Solutions)
.AsQueryable();
.Include(challenge => challenge.Tags)
.Include(challenge => challenge.Author)
.Include(challenge => challenge.Level)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Language)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Solutions)
.ThenInclude(solution => solution.User)
.AsQueryable();

if (!string.IsNullOrEmpty(filters.Title))
{
challenges = challenges.Where(p => p.Title.ToLower().Contains(filters.Title.ToLower()));
var title = Uri.UnescapeDataString(filters.Title);
challenges = challenges.Where(p => p.Title.ToLower().Contains(title.ToLower()));
}

if (filters.ChallengeStatus.HasValue)
{
challenges = challenges.Where(challenge =>
challenges = challenges.Where(challenge =>
challenge.Versions.Any(version => version.Status == filters.ChallengeStatus));
}

Expand All @@ -40,27 +56,116 @@ public async Task<ICollection<ChallengePreviewDto>> GetChallengesAsync(Challenge
challenge.Versions.Any(version => version.LanguageId == filters.LanguageId));
}

if (filters.TagsIds != null)
if (filters.Progress.HasValue)
{
challenges = FilterChallengesByProgress(challenges, filters.Progress);
}

if (filters.TagsIds is not null)
{
var filterTags = _context.Tags.Where(tag =>
filters.TagsIds.Contains(tag.Id));

challenges = challenges.Where(challenge =>
challenge.Tags.Any(tag => filters.TagsIds.Contains(tag.Id)));
filterTags.All(tag => challenge.Tags.Contains(tag)));
}

if (filters.Progress.HasValue)
if (page is not null && page.PageSize > 0 && page.PageNumber > 0)
{
challenges = filters.Progress switch
{
ChallengesProgress.NotStarted => challenges.Where(challenge => !challenge.Versions.Any(version => version.Solutions.Any())),
ChallengesProgress.Started => challenges.Where(challenge => challenge.Versions.Any(version => version.Solutions.Any())),
ChallengesProgress.Completed => challenges.Where(challenge => challenge.Versions.All(version =>
version.Solutions.All(solution => solution.SubmittedAt.HasValue && solution.SubmittedAt.Value != DateTime.MinValue))),
_ => challenges
};
challenges = challenges.Skip(page.PageSize * (page.PageNumber - 1))
.Take(page.PageSize);
}

return _mapper.Map<List<ChallengePreviewDto>>(await challenges.ToListAsync());
}

public async Task<ChallengePreviewDto> GetChallengeSuggestionAsync(SuggestionSettingsDto settings)
{
var challenges = _context.Challenges
.Include(challenge => challenge.Tags)
.Include(challenge => challenge.Author)
.Include(challenge => challenge.Level)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Language)
.Include(challenge => challenge.Versions)
.ThenInclude(version => version.Solutions)
.ThenInclude(solution => solution.User)
.Where(c => c.Versions.Any(v => v.LanguageId == settings.LanguageId))
.AsQueryable();

challenges = await FilterChallengesBySuggestionType(challenges, settings);

var randomPosition = GetRandomInt(challenges.Count());

return _mapper.Map<ChallengePreviewDto>(await challenges.Skip(randomPosition).FirstOrDefaultAsync());
}

private async Task<LanguageLevel> GetUserLevelAsync(int languageId)
{
var userId = _userIdGetter.CurrentUserId;
var userLevel = await _context
.UserLanguageLevels
.Include(userLevel => userLevel.User)
.Where(userLevel => userLevel.User != null && userLevel.User.Uid == userId && userLevel.LanguageId == languageId)
.FirstOrDefaultAsync();

return userLevel?.Level ?? LanguageLevel.FirstSteps;
}

private IQueryable<Challenge> FilterChallengesByProgress(IQueryable<Challenge> challenges, ChallengesProgress? progress)
{
var userId = _userIdGetter.CurrentUserId;

return progress switch
{
ChallengesProgress.NotStarted => challenges.Where(challenge => challenge.Versions.All(version =>
!version.Solutions.Any() || version.Solutions.All(solution =>
solution.User == null || solution.User.Uid != userId))),
ChallengesProgress.Started => challenges.Where(challenge => challenge.Versions.Any(version =>
version.Solutions.Any(solution =>
solution.User != null && solution.User.Uid == userId && !solution.SubmittedAt.HasValue))),
ChallengesProgress.Completed => challenges.Where(challenge => challenge.Versions.Any(version =>
version.Solutions.Any(solution =>
solution.User != null && solution.User.Uid == userId && solution.SubmittedAt.HasValue))),
_ => challenges
};
}
private async Task<IQueryable<Challenge>> FilterChallengesBySuggestionType(IQueryable<Challenge> challenges, SuggestionSettingsDto settings)
{
var userId = _userIdGetter.CurrentUserId;
var userLevel = await GetUserLevelAsync(settings.LanguageId);
var userNextLevel = userLevel.GetNextLevel();

return settings.SuggestionType switch
{
SuggestionType.Beta => challenges.Where(challenge =>
challenge.Versions.Any(version => version.Status == ChallengeStatus.Beta)),
SuggestionType.Fundamentals => challenges.Where(challenge =>
challenge.Category == ChallengeCategory.Fundamentals),
SuggestionType.RankUp => challenges.Where(challenge =>
challenge.Level != null && challenge.Level.SkillLevel == userNextLevel),
SuggestionType.PracticeAndRepeat => challenges.Where(challenge => challenge.Versions.Any(version =>
version.Solutions.Any(solution => solution.User != null && solution.User.Uid == userId))),
_ => challenges
};
}


private static int GetRandomInt(int maxValue)
{
if (maxValue == 0)
{
return 0;
}

using (var generator = RandomNumberGenerator.Create())
{
var data = new byte[4];
generator.GetBytes(data);
var randomValue = Math.Abs(BitConverter.ToInt32(data, 0));

var filteredChallenges = await challenges.ToListAsync();
return _mapper.Map<List<ChallengePreviewDto>>(filteredChallenges);
return randomValue % maxValue;
}
}

public async Task<ChallengeFullDto> GetChallengeByIdAsync(long id)
Expand All @@ -85,4 +190,4 @@ public async Task<ChallengeFullDto> GetChallengeByIdAsync(long id)
return _mapper.Map<ChallengeFullDto>(challenges);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using LeetWars.Core.DAL.Entities;
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.Challenge
{
Expand All @@ -7,5 +8,7 @@ public class ChallengeDto : AuditEntity<long>
public string Title { get; set; } = string.Empty;
public string Instructions { get; set; } = string.Empty;
public int LevelId { get; set; }
public ChallengeCategory Category { get; set; }

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using LeetWars.Core.Common.DTO.Tag;
using LeetWars.Core.Common.DTO.User;
using LeetWars.Core.DAL.Entities;
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.Challenge
{
Expand All @@ -11,6 +12,7 @@ public class ChallengeFullDto : AuditEntity<long>
public string Title { get; set; } = string.Empty;
public string Instructions { get; set; } = string.Empty;
public int LevelId { get; set; }
public ChallengeCategory Category { get; set; }
public ChallengeLevelDto? Level { get; set; }
public UserDto? Author { get; set; }
public ICollection<TagDto>? Tags { get; set; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
using LeetWars.Core.Common.DTO.Language;
using LeetWars.Core.Common.DTO.Tag;
using LeetWars.Core.DAL.Entities;
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.Challenge
{
public class ChallengePreviewDto : Entity<long>
{
public string AuthorName { get; set; } = string.Empty;

public string LevelName { get; set; } = string.Empty;

public string Title { get; set; } = string.Empty;

public string Instructions { get; set; } = string.Empty;

public ChallengeCategory Category { get; set; }

public ICollection<TagDto>? Tags { get; set; }

public ICollection<LanguageDto>? Languages { get; set; }

public int Status { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
namespace LeetWars.Core.DAL.Entities
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.DAL.Entities
{
public class NewChallengeDto
{
public long? CreatedBy { get; set; }
public string Title { get; set; } = string.Empty;
public string Instructions { get; set; } = string.Empty;
public int LevelId { get; set; }
public ChallengeCategory Category { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using LeetWars.Core.DAL.Entities;
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.ChallengeLevel
{
public class ChallengeLevelDto : Entity<int>
{
public string Name { get; set; } = string.Empty;
public int Reward { get; set; }
public LanguageLevel SkillLevel { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
namespace LeetWars.Core.Common.DTO.ChallengeLevel
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.ChallengeLevel
{
public class NewChallengeLevelDto
{
public string Name { get; set; } = string.Empty;
public int Reward { get; set; }
public LanguageLevel SkillLevel { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace LeetWars.Core.Common.DTO.Filters;

public class PageSettingsDto
{
public int PageSize { get; set; }
public int PageNumber { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.Common.DTO.Filters;

public class SuggestionSettingsDto
{
public int LanguageId { get; set; }
public SuggestionType SuggestionType { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void Configure(EntityTypeBuilder<User> builder)
.UsingEntity("PreferredLanguages");

builder.HasMany(e => e.LanguagesWithLevels)
.WithOne()
.WithOne(e => e.User)
.HasForeignKey(e => e.UserId);

builder.HasMany(e => e.Subscriptions)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
namespace LeetWars.Core.DAL.Entities
using LeetWars.Core.DAL.Enums;

namespace LeetWars.Core.DAL.Entities
{
public class Challenge : AuditEntity<long>
{
public string Title { get; set; }
public string Instructions { get; set; }
public int LevelId { get; set; }
public ChallengeLevel? Level { get; set; }

public ChallengeCategory Category { get; set; }
public User? Author { get; set; }
public ICollection<Tag> Tags { get; } = new List<Tag>();
public ICollection<ChallengeVersion> Versions { get; } = new List<ChallengeVersion>();
Expand Down
Loading

0 comments on commit 17e6800

Please sign in to comment.