diff --git a/WotBlitzStatisticsPro.Common/Model/AccountAchievementsResponse.cs b/WotBlitzStatisticsPro.Common/Model/AccountAchievementsResponse.cs new file mode 100644 index 0000000..f1155cd --- /dev/null +++ b/WotBlitzStatisticsPro.Common/Model/AccountAchievementsResponse.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using WotBlitzStatisticsPro.Common.Model.Achievements; + +namespace WotBlitzStatisticsPro.Common.Model +{ + /// + /// Represents information about player's achievements + /// + public class AccountAchievementsResponse + { + /// + /// Player Id + /// + public long AccountId { get; set; } + + /// + /// Player Achievements by sections + /// + public List? AchievementSections { get; set; } + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Common/Model/Achievements/Achievement.cs b/WotBlitzStatisticsPro.Common/Model/Achievements/Achievement.cs new file mode 100644 index 0000000..9ee7b5b --- /dev/null +++ b/WotBlitzStatisticsPro.Common/Model/Achievements/Achievement.cs @@ -0,0 +1,64 @@ +namespace WotBlitzStatisticsPro.Common.Model.Achievements +{ + /// + /// Player's achievement information + /// + public class Achievement + { + /// + /// Achievement Id + /// + public string Id { get; set; } = string.Empty; + + /// + /// Achievement value + /// + public int AchievementValue { get; set; } + + /// + /// Achievement max series value + /// + public int? MaxSeriesValue { get; set; } + + /// + /// Achievement condition + /// + public string? Condition { get; set; } + + /// + /// Achievement description + /// + public string? Description { get; set; } + + /// + /// Achievement normal sized image + /// + public string? Image { get; set; } + + /// + /// Achievement big image + /// + public string? ImageBig { get; set; } + + /// + /// Achievement name + /// + public string? Name { get; set; } + + /// + /// Order + /// + public long? Order { get; set; } + + /// + /// Section Id + /// + public string SectionId { get; set; } = string.Empty; + + /// + /// Achievement type + /// + public string Type { get; set; } = string.Empty; + + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Common/Model/Achievements/AchievementSection.cs b/WotBlitzStatisticsPro.Common/Model/Achievements/AchievementSection.cs new file mode 100644 index 0000000..4a0dd66 --- /dev/null +++ b/WotBlitzStatisticsPro.Common/Model/Achievements/AchievementSection.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; + +namespace WotBlitzStatisticsPro.Common.Model.Achievements +{ + /// + /// Represents achievement section info and it's achievements list + /// + public class AchievementSection + { + /// + /// Section Identifier + /// + public string SectionId { get; set; } = string.Empty; + + /// + /// Section order + /// + public int Order { get; set; } + + /// + /// Section Name + /// + public string Name { get; set; } = string.Empty; + + /// + /// Achievements in the section + /// + public List? Achievements { get; set; } + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Common/Model/ClanInfoResponse.cs b/WotBlitzStatisticsPro.Common/Model/ClanInfoResponse.cs index d1b8cc6..89e9a18 100644 --- a/WotBlitzStatisticsPro.Common/Model/ClanInfoResponse.cs +++ b/WotBlitzStatisticsPro.Common/Model/ClanInfoResponse.cs @@ -18,10 +18,15 @@ namespace WotBlitzStatisticsPro.Common.Model public DateTime JoinedAt { get; set; } = new DateTime(1970, 1, 1); /// - /// Player clan role + /// Player clan role ID /// public string? Role { get; set; } + /// + /// Player clan role + /// + public string RoleLocalized { get; set; } + /// /// Clan name diff --git a/WotBlitzStatisticsPro.DataAccess/DictionariesDataAccessor.cs b/WotBlitzStatisticsPro.DataAccess/DictionariesDataAccessor.cs index 77d6969..7bb9703 100644 --- a/WotBlitzStatisticsPro.DataAccess/DictionariesDataAccessor.cs +++ b/WotBlitzStatisticsPro.DataAccess/DictionariesDataAccessor.cs @@ -153,5 +153,21 @@ namespace WotBlitzStatisticsPro.DataAccess return collection.ToDictionary(n => n.ClanRoleId, n => n.ClanRoleValueValue); } + + public async Task> GetAchievementSections() + { + var collection = await _database.GetCollection(AchievementsSectionsCollectionName) + .FindAsync(Builders.Filter.Empty); + return collection.ToList(); + } + + public async Task> GetAchievements(string[] achievementIds) + { + var collection = await _database.GetCollection(AchievementsCollectionName) + .Find(Builders.Filter.In(v => v.AchievementId, achievementIds)) + .ToListAsync(); + + return collection.ToDictionary(a => a.AchievementId, a => a); + } } } \ No newline at end of file diff --git a/WotBlitzStatisticsPro.DataAccess/IDictionariesDataAccessor.cs b/WotBlitzStatisticsPro.DataAccess/IDictionariesDataAccessor.cs index 0f4a5c9..c4dd52d 100644 --- a/WotBlitzStatisticsPro.DataAccess/IDictionariesDataAccessor.cs +++ b/WotBlitzStatisticsPro.DataAccess/IDictionariesDataAccessor.cs @@ -2,6 +2,7 @@ using System.Threading.Tasks; using WotBlitzStatisticsPro.Common.Dictionaries; using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.DataAccess.Model; namespace WotBlitzStatisticsPro.DataAccess { @@ -32,5 +33,9 @@ namespace WotBlitzStatisticsPro.DataAccess Task> GetTankTypes(RequestLanguage language); Task> GetClanRoles(RequestLanguage language); + + Task> GetAchievementSections(); + + Task> GetAchievements(string[] achievementIds); } } \ No newline at end of file diff --git a/WotBlitzStatisticsPro.GraphQl/Query/WotBlitzAchievementsQuery.cs b/WotBlitzStatisticsPro.GraphQl/Query/WotBlitzAchievementsQuery.cs new file mode 100644 index 0000000..b666794 --- /dev/null +++ b/WotBlitzStatisticsPro.GraphQl/Query/WotBlitzAchievementsQuery.cs @@ -0,0 +1,49 @@ +using System.Threading.Tasks; +using HotChocolate.Types; +using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.Logic; + +namespace WotBlitzStatisticsPro.GraphQl.Query +{ + [ExtendObjectType(Name = "Query")] + public class WotBlitzAchievementsQuery + { + private readonly IWargamingAchievements _achievementsService; + + public WotBlitzAchievementsQuery(IWargamingAchievements achievementsService) + { + _achievementsService = achievementsService; + } + + /// + /// Returns information about player's achievements + /// + /// Account region + /// AccountId + /// Request language + public Task GetAccountAchievements( + long accountId, + RealmType? realmType, + RequestLanguage? requestLanguage) + { + return _achievementsService.GetAccountAchievements(realmType ?? RealmType.Ru, accountId, requestLanguage ?? RequestLanguage.En); + } + + /// + /// Returns information about player's tank achievements + /// + /// Account tank Id + /// Account region + /// AccountId + /// Request language + public Task GetTankAchievements( + long accountId, + long tankId, + RealmType? realmType, + RequestLanguage? requestLanguage) + { + return _achievementsService.GetTankAchievements(realmType ?? RealmType.Ru, accountId, tankId, requestLanguage ?? RequestLanguage.En); + } + + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.GraphQl/Startup.cs b/WotBlitzStatisticsPro.GraphQl/Startup.cs index 42a37b7..548748d 100644 --- a/WotBlitzStatisticsPro.GraphQl/Startup.cs +++ b/WotBlitzStatisticsPro.GraphQl/Startup.cs @@ -45,6 +45,7 @@ namespace WotBlitzStatisticsPro.GraphQl .AddQueryType(d => d.Name("Query")) .AddType() .AddType() + .AddType() .AddMutationType() .AddType() .AddType() diff --git a/WotBlitzStatisticsPro.Logic/IWargamingAchievements.cs b/WotBlitzStatisticsPro.Logic/IWargamingAchievements.cs new file mode 100644 index 0000000..126ac99 --- /dev/null +++ b/WotBlitzStatisticsPro.Logic/IWargamingAchievements.cs @@ -0,0 +1,15 @@ +using System.Threading.Tasks; +using WotBlitzStatisticsPro.Common.Model; + +namespace WotBlitzStatisticsPro.Logic +{ + public interface IWargamingAchievements + { + Task GetAccountAchievements(RealmType realm, long accountId, + RequestLanguage requestLanguage); + + Task GetTankAchievements(RealmType realm, long accountId, + long tankId, + RequestLanguage requestLanguage); + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Logic/Mappers/AchievementDictionaryProfile.cs b/WotBlitzStatisticsPro.Logic/Mappers/AchievementDictionaryProfile.cs deleted file mode 100644 index 6ac64bd..0000000 --- a/WotBlitzStatisticsPro.Logic/Mappers/AchievementDictionaryProfile.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using AutoMapper; -using WotBlitzStatisticsPro.Common.Dictionaries; -using WotBlitzStatisticsPro.DataAccess.Model; -using WotBlitzStatisticsPro.WgApiClient.Model; - -namespace WotBlitzStatisticsPro.Logic.Mappers -{ - public class AchievementDictionaryProfile : Profile - { - public AchievementDictionaryProfile() - { - CreateMap() - .ForMember(d => d.Name, o => o.MapFrom(_ => new List())) - .ForMember(d => d.Condition, o => o.MapFrom(_ => new List())) - .ForMember(d => d.Description, o => o.MapFrom(_ => new List())) - .ForMember(d => d.HeroInfo, o => o.MapFrom(_ => new List())) - .ForMember(d => d.SectionId, o => o.MapFrom(s => s.Section)) - .ForMember(d => d.Options, o => o.Ignore()); - - } - } -} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Logic/Mappers/AchievementOptionProfile.cs b/WotBlitzStatisticsPro.Logic/Mappers/AchievementOptionProfile.cs deleted file mode 100644 index 12f2235..0000000 --- a/WotBlitzStatisticsPro.Logic/Mappers/AchievementOptionProfile.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System.Collections.Generic; -using AutoMapper; -using WotBlitzStatisticsPro.Common.Dictionaries; -using WotBlitzStatisticsPro.WgApiClient.Model; - -namespace WotBlitzStatisticsPro.Logic.Mappers -{ - public class AchievementOptionProfile : Profile - { - public AchievementOptionProfile() - { - CreateMap() - .ForMember(d => d.Name, o => o.MapFrom(_ => new List())) - .ForMember(d => d.Description, o => o.MapFrom(_ => new List())); - } - } -} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Logic/Mappers/AchievementsProfile.cs b/WotBlitzStatisticsPro.Logic/Mappers/AchievementsProfile.cs new file mode 100644 index 0000000..6844a6d --- /dev/null +++ b/WotBlitzStatisticsPro.Logic/Mappers/AchievementsProfile.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using System.Linq; +using AutoMapper; +using WotBlitzStatisticsPro.Common.Dictionaries; +using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.Common.Model.Achievements; +using WotBlitzStatisticsPro.DataAccess.Model; +using WotBlitzStatisticsPro.WgApiClient.Model; + +namespace WotBlitzStatisticsPro.Logic.Mappers +{ + public class AchievementsProfile: Profile + { + public AchievementsProfile() + { + CreateMap() + .ForMember(d => d.Name, o => o.MapFrom(_ => new List())) + .ForMember(d => d.Description, o => o.MapFrom(_ => new List())); + + + CreateMap() + .ForMember(d => d.Name, o => o.MapFrom(_ => new List())) + .ForMember(d => d.Condition, o => o.MapFrom(_ => new List())) + .ForMember(d => d.Description, o => o.MapFrom(_ => new List())) + .ForMember(d => d.HeroInfo, o => o.MapFrom(_ => new List())) + .ForMember(d => d.SectionId, o => o.MapFrom(s => s.Section)) + .ForMember(d => d.Options, o => o.Ignore()); + + CreateMap() + .ForMember(s => s.SectionId, + o => o.MapFrom(d => d.AchievementSectionId)) + .ForMember(d => d.Name, + o => o.MapFrom((src, dest, destMember, context) => + src.AchievementSectionNames + .FirstOrDefault(l => l.Language == (RequestLanguage) context.Items["language"])?.Value)); + + CreateMap, Achievement>() + .ForMember(d => d.Id, o => o.MapFrom(s => s.Key)) + .ForMember(d => d.AchievementValue, o => o.MapFrom(s => s.Value)); + + CreateMap() + .ForMember(d => d.Condition, + o => o.MapFrom((src, dest, destMember, context) => + src.Condition + .FirstOrDefault(l => l.Language == (RequestLanguage) context.Items["language"])?.Value)) + .ForMember(d => d.Description, + o => o.MapFrom((src, dest, destMember, context) => + src.Description + .FirstOrDefault(l => l.Language == (RequestLanguage) context.Items["language"])?.Value)) + .ForMember(d => d.Name, + o => o.MapFrom((src, dest, destMember, context) => + src.Name + .FirstOrDefault(l => l.Language == (RequestLanguage) context.Items["language"])?.Value)) + .ForMember(d => d.Id, o => o.Ignore()) + .ForMember(d => d.AchievementValue, o => o.Ignore()) + .ForMember(d => d.MaxSeriesValue, s => s.Ignore()) + ; + } + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Logic/Mappers/ClansProfile.cs b/WotBlitzStatisticsPro.Logic/Mappers/ClansProfile.cs index 7602c25..c145690 100644 --- a/WotBlitzStatisticsPro.Logic/Mappers/ClansProfile.cs +++ b/WotBlitzStatisticsPro.Logic/Mappers/ClansProfile.cs @@ -1,10 +1,7 @@ using System; using System.Collections.Generic; -using System.Linq; using AutoMapper; -using WotBlitzStatisticsPro.Common.Dictionaries; using WotBlitzStatisticsPro.Common.Model; -using WotBlitzStatisticsPro.DataAccess.Model; using WotBlitzStatisticsPro.WgApiClient.Model; using WotBlitzStatisticsPro.Logic.Calculations; @@ -18,7 +15,7 @@ namespace WotBlitzStatisticsPro.Logic.Mappers CreateMap() .ForMember(d => d.JoinedAt, o => o.MapFrom(s => s.JoinedAt.HasValue ? s.JoinedAt.Value.ToDateTime() : new DateTime(1970, 1, 1))) - .ForMember(d => d.Role, + .ForMember(d => d.RoleLocalized, o => o.MapFrom((src, dest, destMember, context) => { var dictionary = context.Items["roles"] as Dictionary; diff --git a/WotBlitzStatisticsPro.Logic/WargamingAchievements.cs b/WotBlitzStatisticsPro.Logic/WargamingAchievements.cs new file mode 100644 index 0000000..a92883b --- /dev/null +++ b/WotBlitzStatisticsPro.Logic/WargamingAchievements.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using AutoMapper; +using Microsoft.Extensions.Logging; +using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.Common.Model.Achievements; +using WotBlitzStatisticsPro.DataAccess; +using WotBlitzStatisticsPro.DataAccess.Model; +using WotBlitzStatisticsPro.WgApiClient; +using WotBlitzStatisticsPro.WgApiClient.Model; + +namespace WotBlitzStatisticsPro.Logic +{ + public class WargamingAchievements: IWargamingAchievements + { + private readonly IDictionariesDataAccessor _dictionariesDataAccessor; + private readonly IWargamingAchievementsApiClient _wargamingAchievementsService; + private readonly IMapper _mapper; + private readonly ILogger _logger; + + public WargamingAchievements( + IDictionariesDataAccessor dictionariesDataAccessor, + IWargamingAchievementsApiClient wargamingAchievementsService, + IMapper mapper, + ILogger logger) + { + _dictionariesDataAccessor = dictionariesDataAccessor; + _wargamingAchievementsService = wargamingAchievementsService; + _mapper = mapper; + _logger = logger; + } + + public async Task GetAccountAchievements(RealmType realm, long accountId, RequestLanguage requestLanguage) + { + try + { + // Get account achievements from WG API + var wgAchievements = + await _wargamingAchievementsService.GetAccountAchievements(accountId, realm, requestLanguage); + + return await FillAchievements(accountId, wgAchievements, requestLanguage); + } + catch (Exception e) + { + _logger.LogError(e, "GetAccountAchievements error"); + + throw; + } + } + + public async Task GetTankAchievements(RealmType realm, long accountId, long tankId, RequestLanguage requestLanguage) + { + try + { + // Get account achievements from WG API + var wgAchievements = + await _wargamingAchievementsService.GetTankAchievements(accountId, tankId, realm, requestLanguage); + + return await FillAchievements(accountId, wgAchievements, requestLanguage); + } + catch (Exception e) + { + _logger.LogError(e, "GetTankAchievements error"); + + throw; + } + } + + private async Task FillAchievements(long accountId, WotAccountAchievementResponse? wgAchievements, + RequestLanguage requestLanguage) + { + // Read achievement sections from DB + var sectionsDictionary = await _dictionariesDataAccessor.GetAchievementSections(); + var sections = + _mapper.Map, List>(sectionsDictionary, + context => context.Items["language"] = requestLanguage); + + var response = new AccountAchievementsResponse + { + AccountId = accountId, + AchievementSections = sections + }; + + if (wgAchievements?.Achievements == null) + { + return response; + } + + // Map ID And value + var achievements = _mapper.Map, List>(wgAchievements.Achievements); + + // Add Max series + if (wgAchievements.MaxSeries != null) + { + foreach (var achievementsMaxSeries in wgAchievements.MaxSeries) + { + if (achievementsMaxSeries.Value == 0) + { + continue; + } + var existing = achievements.FirstOrDefault(a => a.Id == achievementsMaxSeries.Key); + if (existing == null) + { + achievements.Add(new Achievement + { Id = achievementsMaxSeries.Key, MaxSeriesValue = achievementsMaxSeries.Value }); + } + else + { + existing.MaxSeriesValue = achievementsMaxSeries.Value; + } + } + } + + // Read achievements dictionaries from DB + var achievementDictionaries = + await _dictionariesDataAccessor.GetAchievements(achievements.Select(a => a.Id).ToArray()); + + // Map achievements dictionaries + foreach (var achievement in achievements) + { + _mapper.Map(achievementDictionaries[achievement.Id], achievement, + context => context.Items["language"] = requestLanguage); + + // Map achievement option if exists + if (achievementDictionaries[achievement.Id].Options.Count > 0) + { + if (achievement.AchievementValue < achievementDictionaries[achievement.Id].Options.Count) + { + achievement.Image = achievementDictionaries[achievement.Id] + .Options[achievement.AchievementValue - 1].Image; + achievement.ImageBig = achievementDictionaries[achievement.Id] + .Options[achievement.AchievementValue - 1].ImageBig; + } + } + } + + // Sort achievements by sections + foreach (var section in response.AchievementSections) + { + section.Achievements = achievements.Where(a => a.SectionId == section.SectionId).ToList(); + } + + return response; + } + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.Logic/WotBlitzStatisticsLogicInstaller.cs b/WotBlitzStatisticsPro.Logic/WotBlitzStatisticsLogicInstaller.cs index 8b0df9c..915eefb 100644 --- a/WotBlitzStatisticsPro.Logic/WotBlitzStatisticsLogicInstaller.cs +++ b/WotBlitzStatisticsPro.Logic/WotBlitzStatisticsLogicInstaller.cs @@ -26,10 +26,12 @@ namespace WotBlitzStatisticsPro.Logic services.AddHttpClient(); services.AddHttpClient(); services.AddHttpClient(); + services.AddHttpClient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); } private static void ConfigureDictionariesFactory(IServiceCollection services) diff --git a/WotBlitzStatisticsPro.WgApiClient/IWargamingAchievementsApiClient.cs b/WotBlitzStatisticsPro.WgApiClient/IWargamingAchievementsApiClient.cs new file mode 100644 index 0000000..9d08b88 --- /dev/null +++ b/WotBlitzStatisticsPro.WgApiClient/IWargamingAchievementsApiClient.cs @@ -0,0 +1,20 @@ +using System.Threading.Tasks; +using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.WgApiClient.Model; + +namespace WotBlitzStatisticsPro.WgApiClient +{ + public interface IWargamingAchievementsApiClient + { + Task GetAccountAchievements( + long accountId, + RealmType realmType = RealmType.Ru, + RequestLanguage language = RequestLanguage.En); + + Task GetTankAchievements( + long accountId, + long tankId, + RealmType realmType = RealmType.Ru, + RequestLanguage language = RequestLanguage.En); + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.WgApiClient/Model/WotAccountAchievementResponse.cs b/WotBlitzStatisticsPro.WgApiClient/Model/WotAccountAchievementResponse.cs new file mode 100644 index 0000000..26d03b1 --- /dev/null +++ b/WotBlitzStatisticsPro.WgApiClient/Model/WotAccountAchievementResponse.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace WotBlitzStatisticsPro.WgApiClient.Model +{ + public class WotAccountAchievementResponse + { + /// + /// Achievements + /// + [JsonProperty("achievements")] + public Dictionary? Achievements { get; set; } + + /// + /// Max values if series achievements + /// + [JsonProperty("max_series")] + public Dictionary? MaxSeries { get; set; } + } +} \ No newline at end of file diff --git a/WotBlitzStatisticsPro.WgApiClient/WargamingAchievementsApiClient.cs b/WotBlitzStatisticsPro.WgApiClient/WargamingAchievementsApiClient.cs new file mode 100644 index 0000000..11caa26 --- /dev/null +++ b/WotBlitzStatisticsPro.WgApiClient/WargamingAchievementsApiClient.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using WotBlitzStatisticsPro.Common; +using WotBlitzStatisticsPro.Common.Model; +using WotBlitzStatisticsPro.WgApiClient.Model; + +namespace WotBlitzStatisticsPro.WgApiClient +{ + public class WargamingAchievementsApiClient: WagramingApiClientBase, + IWargamingAchievementsApiClient + { + public WargamingAchievementsApiClient(HttpClient httpClient, IWargamingApiSettings wargamingApiSettings) : base(httpClient, wargamingApiSettings) + { + } + + public async Task GetAccountAchievements(long accountId, RealmType realmType = RealmType.Ru, + RequestLanguage language = RequestLanguage.En) + { + var accountAchievements = await GetFromBlitzApi>( + realmType, + language, + "account/achievements/", + $"account_id={accountId}").ConfigureAwait(false); + return accountAchievements?[accountId.ToString()]; + } + + public async Task GetTankAchievements(long accountId, long tankId, RealmType realmType = RealmType.Ru, + RequestLanguage language = RequestLanguage.En) + { + var accountAchievements = await GetFromBlitzApi>( + realmType, + language, + "tanks/achievements/", + $"account_id={accountId}", + $"tank_id={tankId}").ConfigureAwait(false); + if (accountAchievements?[accountId.ToString()] != null && + accountAchievements?[accountId.ToString()].Length == 1) + { + return accountAchievements?[accountId.ToString()][0]; + } + + return null; + } + } +} \ No newline at end of file