From ff41df4c77f5f5fd7be2d47fb34c3da2303accea Mon Sep 17 00:00:00 2001 From: Michal Halabica Date: Fri, 23 Feb 2024 02:00:36 +0100 Subject: [PATCH 1/4] Concrete data resolvers --- .../Managers/DataResolve/BaseDataResolver.cs | 76 +++++++++++++++++++ .../DataResolve/ChannelDataResolver.cs | 24 ++++++ .../Managers/DataResolve/GuildDataResolver.cs | 20 +++++ .../Managers/DataResolve/GuildUserResolver.cs | 24 ++++++ .../Managers/DataResolve/UserDataResolver.cs | 20 +++++ 5 files changed, 164 insertions(+) create mode 100644 src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs create mode 100644 src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs create mode 100644 src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs create mode 100644 src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs create mode 100644 src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs diff --git a/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs new file mode 100644 index 00000000..19aa6186 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs @@ -0,0 +1,76 @@ +using AutoMapper; +using GrillBot.Database.Services.Repository; + +namespace GrillBot.App.Managers.DataResolve; + +public abstract class BaseDataResolver + : IDisposable where TDiscordEntity : class where TKey : notnull where TDatabaseEntity : class +{ + private readonly Dictionary _cachedData = new(); + private bool disposedValue; + + protected IDiscordClient DiscordClient { get; } + protected GrillBotDatabaseBuilder DatabaseBuilder { get; } + private IMapper Mapper { get; } + + protected BaseDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + { + DiscordClient = discordClient; + Mapper = mapper; + DatabaseBuilder = databaseBuilder; + } + + private TMappedValue MapAndStore(TKey key, TDiscordEntity entity) + { + var mapped = Mapper.Map(entity); + _cachedData[key] = mapped; + + return mapped; + } + + private TMappedValue MapAndStore(TKey key, TDatabaseEntity entity) + { + var mapped = Mapper.Map(entity); + _cachedData[key] = mapped; + + return mapped; + } + + protected async Task GetMappedEntityAsync( + TKey key, + Func> readDiscordEntity, + Func> readDatabaseEntity + ) + { + if (_cachedData.TryGetValue(key, out var value)) + return value; + + var discordEntity = await readDiscordEntity(); + if (discordEntity is not null) + return MapAndStore(key, discordEntity); + + await using var repository = DatabaseBuilder.CreateRepository(); + + var databaseEntity = await readDatabaseEntity(repository); + return databaseEntity is null ? default : MapAndStore(key, databaseEntity); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _cachedData.Clear(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } +} diff --git a/src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs new file mode 100644 index 00000000..e19b7e86 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs @@ -0,0 +1,24 @@ +using AutoMapper; + +namespace GrillBot.App.Managers.DataResolve; + +public class ChannelDataResolver : BaseDataResolver, IGuildChannel, Database.Entity.GuildChannel, Data.Models.API.Channels.Channel> +{ + public ChannelDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + : base(discordClient, mapper, databaseBuilder) + { + } + + public Task GetChannelAsync(ulong guildId, ulong channelId) + { + return GetMappedEntityAsync( + Tuple.Create(guildId, channelId), + async () => + { + var guild = await DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); + return guild is null ? null : await guild.GetChannelAsync(channelId, CacheMode.CacheOnly); + }, + repo => repo.Channel.FindChannelByIdAsync(channelId, guildId, true) + ); + } +} diff --git a/src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs new file mode 100644 index 00000000..06e07550 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs @@ -0,0 +1,20 @@ +using AutoMapper; + +namespace GrillBot.App.Managers.DataResolve; + +public class GuildDataResolver : BaseDataResolver +{ + public GuildDataResolver(IDiscordClient discordClient, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper) + : base(discordClient, mapper, databaseBuilder) + { + } + + public Task GetGuildAsync(ulong guildId) + { + return GetMappedEntityAsync( + guildId, + () => DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly), + repo => repo.Guild.FindGuildByIdAsync(guildId, true) + ); + } +} diff --git a/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs b/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs new file mode 100644 index 00000000..913af021 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs @@ -0,0 +1,24 @@ +using AutoMapper; + +namespace GrillBot.App.Managers.DataResolve; + +public class GuildUserResolver : BaseDataResolver, IGuildUser, Database.Entity.GuildUser, Data.Models.API.Users.GuildUser> +{ + public GuildUserResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + : base(discordClient, mapper, databaseBuilder) + { + } + + public Task GetGuildUserAsync(ulong guildId, ulong userId) + { + return GetMappedEntityAsync( + Tuple.Create(guildId, userId), + async () => + { + var guild = await DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); + return guild is null ? null : await guild.GetUserAsync(userId, CacheMode.CacheOnly); + }, + repo => repo.GuildUser.FindGuildUserByIdAsync(guildId, userId, true) + ); + } +} diff --git a/src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs new file mode 100644 index 00000000..eb2e0400 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs @@ -0,0 +1,20 @@ +using AutoMapper; + +namespace GrillBot.App.Managers.DataResolve; + +public class UserDataResolver : BaseDataResolver +{ + public UserDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + : base(discordClient, mapper, databaseBuilder) + { + } + + public Task GetUserAsync(ulong userId) + { + return GetMappedEntityAsync( + userId, + () => DiscordClient.GetUserAsync(userId, CacheMode.CacheOnly), + repo => repo.User.FindUserByIdAsync(userId, disableTracking: true) + ); + } +} From 9d416c2fc5f02d863109f738d9e61804630b7c4a Mon Sep 17 00:00:00 2001 From: Michal Halabica Date: Fri, 23 Feb 2024 21:04:33 +0100 Subject: [PATCH 2/4] Integrated DataResolveManager into GetAuditLogDetail --- .../Api/V1/AuditLog/GetAuditLogDetail.cs | 40 ++++----- ...nnelDataResolver.cs => ChannelResolver.cs} | 4 +- .../DataResolve/DataResolveManager.cs | 83 +++++++++++++++++++ ...{GuildDataResolver.cs => GuildResolver.cs} | 4 +- .../Managers/DataResolve/RoleResolver.cs | 20 +++++ .../{UserDataResolver.cs => UserResolver.cs} | 4 +- .../Managers/ManagerExtensions.cs | 3 +- .../Models/API/Users/UsersMappingProfile.cs | 3 +- 8 files changed, 134 insertions(+), 27 deletions(-) rename src/GrillBot.App/Managers/DataResolve/{ChannelDataResolver.cs => ChannelResolver.cs} (71%) create mode 100644 src/GrillBot.App/Managers/DataResolve/DataResolveManager.cs rename src/GrillBot.App/Managers/DataResolve/{GuildDataResolver.cs => GuildResolver.cs} (65%) create mode 100644 src/GrillBot.App/Managers/DataResolve/RoleResolver.cs rename src/GrillBot.App/Managers/DataResolve/{UserDataResolver.cs => UserResolver.cs} (66%) diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs index cfd73375..38d3388b 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs @@ -1,6 +1,6 @@ using System.Text.Json; using AutoMapper; -using GrillBot.Common.Extensions.Discord; +using GrillBot.App.Managers.DataResolve; using GrillBot.Common.Models; using GrillBot.Core.Extensions; using GrillBot.Core.Infrastructure.Actions; @@ -13,17 +13,15 @@ namespace GrillBot.App.Actions.Api.V1.AuditLog; public class GetAuditLogDetail : ApiAction { private IAuditLogServiceClient AuditLogServiceClient { get; } - private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IDiscordClient DiscordClient { get; } private IMapper Mapper { get; } - public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, GrillBotDatabaseBuilder databaseBuilder, IDiscordClient discordClient, - IMapper mapper) : base(apiContext) + private readonly DataResolveManager _dataResolve; + + public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, IMapper mapper, DataResolveManager dataResolve) : base(apiContext) { AuditLogServiceClient = auditLogServiceClient; - DatabaseBuilder = databaseBuilder; - DiscordClient = discordClient; Mapper = mapper; + _dataResolve = dataResolve; } public override async Task ProcessAsync() @@ -39,8 +37,6 @@ public override async Task ProcessAsync() DictionaryKeyPolicy = JsonNamingPolicy.CamelCase }; - await using var repository = DatabaseBuilder.CreateRepository(); - switch (detail.Type) { case LogType.Info or LogType.Warning or LogType.Error: @@ -52,22 +48,25 @@ public override async Task ProcessAsync() case LogType.OverwriteUpdated: { var overwriteUpdated = jsonElement.Deserialize(options)!; - var role = overwriteUpdated.TargetType == PermissionTarget.Role ? await DiscordClient.FindRoleAsync(overwriteUpdated.TargetId.ToUlong()) : null; - var user = overwriteUpdated.TargetType == PermissionTarget.User ? await repository.User.FindUserByIdAsync(overwriteUpdated.TargetId.ToUlong()) : null; - detail.Data = new Data.Models.API.AuditLog.Detail.OverwriteUpdatedDetail + var detailData = new Data.Models.API.AuditLog.Detail.OverwriteUpdatedDetail { - User = Mapper.Map(user), - Role = Mapper.Map(role), Allow = overwriteUpdated.Allow, Deny = overwriteUpdated.Deny }; + + if (overwriteUpdated.TargetType is PermissionTarget.Role) + detailData.Role = await _dataResolve.GetRoleAsync(overwriteUpdated.TargetId.ToUlong()); + if (overwriteUpdated.TargetType is PermissionTarget.User) + detailData.User = await _dataResolve.GetUserAsync(overwriteUpdated.TargetId.ToUlong()); + + detail.Data = detailData; break; } case LogType.MemberUpdated: { var memberUpdated = jsonElement.Deserialize(options)!; - var user = await repository.User.FindUserByIdAsync(memberUpdated.UserId.ToUlong()); + var user = await _dataResolve.GetUserAsync(memberUpdated.UserId.ToUlong()); detail.Data = new Data.Models.API.AuditLog.Detail.MemberUpdatedDetail { @@ -86,7 +85,7 @@ public override async Task ProcessAsync() case LogType.MessageDeleted: { var messageDeleted = jsonElement.Deserialize(options)!; - var author = await repository.User.FindUserByIdAsync(messageDeleted.AuthorId.ToUlong()); + var author = await _dataResolve.GetUserAsync(messageDeleted.AuthorId.ToUlong()); detail.Data = new Data.Models.API.AuditLog.Detail.MessageDeletedDetail { @@ -106,17 +105,20 @@ public override async Task ProcessAsync() case LogType.JobCompleted: { var jobCompleted = jsonElement.Deserialize(options)!; - var startUser = string.IsNullOrEmpty(jobCompleted.StartUserId) ? null : await repository.User.FindUserByIdAsync(jobCompleted.StartUserId.ToUlong()); - detail.Data = new Data.Models.API.AuditLog.Detail.JobExecutionDetail + var detailData = new Data.Models.API.AuditLog.Detail.JobExecutionDetail { Result = jobCompleted.Result, EndAt = jobCompleted.EndAt.ToLocalTime(), JobName = jobCompleted.JobName, StartAt = jobCompleted.StartAt.ToLocalTime(), - StartUser = startUser is null ? null : Mapper.Map(startUser), WasError = jobCompleted.WasError }; + + if (!string.IsNullOrEmpty(jobCompleted.StartUserId)) + detailData.StartUser = await _dataResolve.GetUserAsync(jobCompleted.StartUserId.ToUlong()); + + detail.Data = detailData; break; } case LogType.Api: diff --git a/src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs similarity index 71% rename from src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs rename to src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs index e19b7e86..6bbc89cf 100644 --- a/src/GrillBot.App/Managers/DataResolve/ChannelDataResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs @@ -2,9 +2,9 @@ namespace GrillBot.App.Managers.DataResolve; -public class ChannelDataResolver : BaseDataResolver, IGuildChannel, Database.Entity.GuildChannel, Data.Models.API.Channels.Channel> +public class ChannelResolver : BaseDataResolver, IGuildChannel, Database.Entity.GuildChannel, Data.Models.API.Channels.Channel> { - public ChannelDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + public ChannelResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) : base(discordClient, mapper, databaseBuilder) { } diff --git a/src/GrillBot.App/Managers/DataResolve/DataResolveManager.cs b/src/GrillBot.App/Managers/DataResolve/DataResolveManager.cs new file mode 100644 index 00000000..92788078 --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/DataResolveManager.cs @@ -0,0 +1,83 @@ +using AutoMapper; +using System.Diagnostics.CodeAnalysis; +using ApiModels = GrillBot.Data.Models.API; + +namespace GrillBot.App.Managers.DataResolve; + +public class DataResolveManager : IDisposable +{ + private readonly IDiscordClient _discordClient; + private readonly GrillBotDatabaseBuilder _databaseBuilder; + private readonly IMapper _mapper; + + [MaybeNull] private GuildResolver _guildResolver; + [MaybeNull] private GuildUserResolver _guildUserResolver; + [MaybeNull] private ChannelResolver _channelResolver; + [MaybeNull] private UserResolver _userResolver; + [MaybeNull] private RoleResolver _roleResolver; + private bool disposedValue; + + public DataResolveManager(IDiscordClient discordClient, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper) + { + _discordClient = discordClient; + _databaseBuilder = databaseBuilder; + _mapper = mapper; + } + + public async Task GetChannelAsync(ulong guildId, ulong channelId) + { + _channelResolver ??= new ChannelResolver(_discordClient, _mapper, _databaseBuilder); + return await _channelResolver.GetChannelAsync(guildId, channelId); + } + + public async Task GetGuildAsync(ulong guildId) + { + _guildResolver ??= new GuildResolver(_discordClient, _mapper, _databaseBuilder); + return await _guildResolver.GetGuildAsync(guildId); + } + + public async Task GetGuildUserAsync(ulong guildId, ulong userId) + { + _guildUserResolver ??= new GuildUserResolver(_discordClient, _mapper, _databaseBuilder); + return await _guildUserResolver.GetGuildUserAsync(guildId, userId); + } + + public async Task GetUserAsync(ulong userId) + { + _userResolver ??= new UserResolver(_discordClient, _mapper, _databaseBuilder); + return await _userResolver.GetUserAsync(userId); + } + + public async Task GetRoleAsync(ulong roleId) + { + _roleResolver ??= new RoleResolver(_discordClient, _mapper, _databaseBuilder); + return await _roleResolver.GetRoleAsync(roleId); + } + + #region Disposable + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _guildResolver?.Dispose(); + _guildUserResolver?.Dispose(); + _channelResolver?.Dispose(); + _userResolver?.Dispose(); + _roleResolver?.Dispose(); + } + + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + #endregion +} diff --git a/src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs similarity index 65% rename from src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs rename to src/GrillBot.App/Managers/DataResolve/GuildResolver.cs index 06e07550..d01cc2b9 100644 --- a/src/GrillBot.App/Managers/DataResolve/GuildDataResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs @@ -2,9 +2,9 @@ namespace GrillBot.App.Managers.DataResolve; -public class GuildDataResolver : BaseDataResolver +public class GuildResolver : BaseDataResolver { - public GuildDataResolver(IDiscordClient discordClient, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper) + public GuildResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) : base(discordClient, mapper, databaseBuilder) { } diff --git a/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs b/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs new file mode 100644 index 00000000..34193dea --- /dev/null +++ b/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs @@ -0,0 +1,20 @@ +using AutoMapper; + +namespace GrillBot.App.Managers.DataResolve; + +public class RoleResolver : BaseDataResolver +{ + public RoleResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + : base(discordClient, mapper, databaseBuilder) + { + } + + public Task GetRoleAsync(ulong roleId) + { + return GetMappedEntityAsync( + roleId, + async () => (await DiscordClient.GetGuildsAsync(CacheMode.CacheOnly)).Select(o => o.GetRole(roleId)).FirstOrDefault(o => o is not null), + _ => Task.FromResult(null) + ); + } +} diff --git a/src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/UserResolver.cs similarity index 66% rename from src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs rename to src/GrillBot.App/Managers/DataResolve/UserResolver.cs index eb2e0400..12c9c332 100644 --- a/src/GrillBot.App/Managers/DataResolve/UserDataResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/UserResolver.cs @@ -2,9 +2,9 @@ namespace GrillBot.App.Managers.DataResolve; -public class UserDataResolver : BaseDataResolver +public class UserResolver : BaseDataResolver { - public UserDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) + public UserResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) : base(discordClient, mapper, databaseBuilder) { } diff --git a/src/GrillBot.App/Managers/ManagerExtensions.cs b/src/GrillBot.App/Managers/ManagerExtensions.cs index 85484e49..4f47a3dd 100644 --- a/src/GrillBot.App/Managers/ManagerExtensions.cs +++ b/src/GrillBot.App/Managers/ManagerExtensions.cs @@ -16,7 +16,8 @@ public static IServiceCollection AddManagers(this IServiceCollection services) .AddScoped() .AddScoped() .AddSingleton() - .AddScoped(); + .AddScoped() + .AddScoped(); return services; } diff --git a/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs b/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs index 67faa485..df86a8f2 100644 --- a/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs +++ b/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs @@ -11,7 +11,8 @@ public class UsersMappingProfile : AutoMapper.Profile public UsersMappingProfile() { CreateMap() - .ForMember(dst => dst.AvatarUrl, opt => opt.MapFrom(src => src.GetUserAvatarUrl(128))); + .ForMember(dst => dst.AvatarUrl, opt => opt.MapFrom(src => src.GetUserAvatarUrl(128))) + .ForMember(dst => dst.GlobalAlias, opt => opt.MapFrom(src => src.GlobalName)); CreateMap() .ForMember(dst => dst.IsBot, opt => opt.MapFrom(src => src.HaveFlags(UserFlags.NotUser))); From 3e7da58deffb3003ff768d82938626153b23c09c Mon Sep 17 00:00:00 2001 From: Michal Halabica Date: Fri, 23 Feb 2024 21:06:10 +0100 Subject: [PATCH 3/4] Use readonly fields instead of properties --- .../Managers/DataResolve/BaseDataResolver.cs | 18 +++++++++--------- .../Managers/DataResolve/ChannelResolver.cs | 2 +- .../Managers/DataResolve/GuildResolver.cs | 2 +- .../Managers/DataResolve/GuildUserResolver.cs | 2 +- .../Managers/DataResolve/RoleResolver.cs | 2 +- .../Managers/DataResolve/UserResolver.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs b/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs index 19aa6186..dfba80e2 100644 --- a/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/BaseDataResolver.cs @@ -9,20 +9,20 @@ public abstract class BaseDataResolver _cachedData = new(); private bool disposedValue; - protected IDiscordClient DiscordClient { get; } - protected GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } + protected readonly IDiscordClient _discordClient; + protected readonly GrillBotDatabaseBuilder _databaseBuilder; + private readonly IMapper _mapper; protected BaseDataResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatabaseBuilder databaseBuilder) { - DiscordClient = discordClient; - Mapper = mapper; - DatabaseBuilder = databaseBuilder; + _discordClient = discordClient; + _mapper = mapper; + _databaseBuilder = databaseBuilder; } private TMappedValue MapAndStore(TKey key, TDiscordEntity entity) { - var mapped = Mapper.Map(entity); + var mapped = _mapper.Map(entity); _cachedData[key] = mapped; return mapped; @@ -30,7 +30,7 @@ private TMappedValue MapAndStore(TKey key, TDiscordEntity entity) private TMappedValue MapAndStore(TKey key, TDatabaseEntity entity) { - var mapped = Mapper.Map(entity); + var mapped = _mapper.Map(entity); _cachedData[key] = mapped; return mapped; @@ -49,7 +49,7 @@ private TMappedValue MapAndStore(TKey key, TDatabaseEntity entity) if (discordEntity is not null) return MapAndStore(key, discordEntity); - await using var repository = DatabaseBuilder.CreateRepository(); + await using var repository = _databaseBuilder.CreateRepository(); var databaseEntity = await readDatabaseEntity(repository); return databaseEntity is null ? default : MapAndStore(key, databaseEntity); diff --git a/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs index 6bbc89cf..42e40e0b 100644 --- a/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs @@ -15,7 +15,7 @@ public ChannelResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDat Tuple.Create(guildId, channelId), async () => { - var guild = await DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); + var guild = await _discordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); return guild is null ? null : await guild.GetChannelAsync(channelId, CacheMode.CacheOnly); }, repo => repo.Channel.FindChannelByIdAsync(channelId, guildId, true) diff --git a/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs b/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs index d01cc2b9..40cd03d1 100644 --- a/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/GuildResolver.cs @@ -13,7 +13,7 @@ public GuildResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDatab { return GetMappedEntityAsync( guildId, - () => DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly), + () => _discordClient.GetGuildAsync(guildId, CacheMode.CacheOnly), repo => repo.Guild.FindGuildByIdAsync(guildId, true) ); } diff --git a/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs b/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs index 913af021..47376164 100644 --- a/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/GuildUserResolver.cs @@ -15,7 +15,7 @@ public GuildUserResolver(IDiscordClient discordClient, IMapper mapper, GrillBotD Tuple.Create(guildId, userId), async () => { - var guild = await DiscordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); + var guild = await _discordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); return guild is null ? null : await guild.GetUserAsync(userId, CacheMode.CacheOnly); }, repo => repo.GuildUser.FindGuildUserByIdAsync(guildId, userId, true) diff --git a/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs b/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs index 34193dea..d4209337 100644 --- a/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/RoleResolver.cs @@ -13,7 +13,7 @@ public RoleResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDataba { return GetMappedEntityAsync( roleId, - async () => (await DiscordClient.GetGuildsAsync(CacheMode.CacheOnly)).Select(o => o.GetRole(roleId)).FirstOrDefault(o => o is not null), + async () => (await _discordClient.GetGuildsAsync(CacheMode.CacheOnly)).Select(o => o.GetRole(roleId)).FirstOrDefault(o => o is not null), _ => Task.FromResult(null) ); } diff --git a/src/GrillBot.App/Managers/DataResolve/UserResolver.cs b/src/GrillBot.App/Managers/DataResolve/UserResolver.cs index 12c9c332..1baa960f 100644 --- a/src/GrillBot.App/Managers/DataResolve/UserResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/UserResolver.cs @@ -13,7 +13,7 @@ public UserResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDataba { return GetMappedEntityAsync( userId, - () => DiscordClient.GetUserAsync(userId, CacheMode.CacheOnly), + () => _discordClient.GetUserAsync(userId, CacheMode.CacheOnly), repo => repo.User.FindUserByIdAsync(userId, disableTracking: true) ); } From 40a08b7d494df20bacfd75181579cc5f5cd5df57 Mon Sep 17 00:00:00 2001 From: Michal Halabica Date: Fri, 23 Feb 2024 22:25:12 +0100 Subject: [PATCH 4/4] Fully integrated DataResolveManager --- .../Api/V1/AuditLog/GetAuditLogDetail.cs | 13 +- .../Api/V1/AuditLog/GetAuditLogList.cs | 137 +++++------------- .../V1/Dashboard/GetUserMeasuresDashboard.cs | 51 ++++--- .../Api/V1/Points/ComputeUserPoints.cs | 40 ++--- .../Api/V1/Points/GetPointsLeaderboard.cs | 30 ++-- .../Api/V1/Points/GetTransactionList.cs | 36 ++--- .../Actions/Api/V1/Points/GetUserList.cs | 67 ++------- .../Api/V1/Statistics/GetApiUserStatistics.cs | 42 +++--- .../V1/Statistics/GetUserCommandStatistics.cs | 38 +++-- .../Actions/Api/V1/User/GetUserDetail.cs | 29 ++-- .../V1/UserMeasures/GetUserMeasuresList.cs | 57 ++------ .../Commands/Points/PointsLeaderboard.cs | 32 ++-- .../Managers/DataResolve/ChannelResolver.cs | 2 +- .../Models/API/Users/GuildUser.cs | 4 +- .../Models/API/Users/UsersMappingProfile.cs | 5 + 15 files changed, 219 insertions(+), 364 deletions(-) diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs index 38d3388b..4ddc8b62 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogDetail.cs @@ -1,5 +1,4 @@ using System.Text.Json; -using AutoMapper; using GrillBot.App.Managers.DataResolve; using GrillBot.Common.Models; using GrillBot.Core.Extensions; @@ -13,14 +12,12 @@ namespace GrillBot.App.Actions.Api.V1.AuditLog; public class GetAuditLogDetail : ApiAction { private IAuditLogServiceClient AuditLogServiceClient { get; } - private IMapper Mapper { get; } private readonly DataResolveManager _dataResolve; - public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, IMapper mapper, DataResolveManager dataResolve) : base(apiContext) + public GetAuditLogDetail(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, DataResolveManager dataResolve) : base(apiContext) { AuditLogServiceClient = auditLogServiceClient; - Mapper = mapper; _dataResolve = dataResolve; } @@ -57,7 +54,7 @@ public override async Task ProcessAsync() if (overwriteUpdated.TargetType is PermissionTarget.Role) detailData.Role = await _dataResolve.GetRoleAsync(overwriteUpdated.TargetId.ToUlong()); - if (overwriteUpdated.TargetType is PermissionTarget.User) + else if (overwriteUpdated.TargetType is PermissionTarget.User) detailData.User = await _dataResolve.GetUserAsync(overwriteUpdated.TargetId.ToUlong()); detail.Data = detailData; @@ -66,11 +63,10 @@ public override async Task ProcessAsync() case LogType.MemberUpdated: { var memberUpdated = jsonElement.Deserialize(options)!; - var user = await _dataResolve.GetUserAsync(memberUpdated.UserId.ToUlong()); detail.Data = new Data.Models.API.AuditLog.Detail.MemberUpdatedDetail { - User = Mapper.Map(user), + User = (await _dataResolve.GetUserAsync(memberUpdated.UserId.ToUlong()))!, Flags = memberUpdated.Flags, Nickname = memberUpdated.Nickname, IsDeaf = memberUpdated.IsDeaf, @@ -85,11 +81,10 @@ public override async Task ProcessAsync() case LogType.MessageDeleted: { var messageDeleted = jsonElement.Deserialize(options)!; - var author = await _dataResolve.GetUserAsync(messageDeleted.AuthorId.ToUlong()); detail.Data = new Data.Models.API.AuditLog.Detail.MessageDeletedDetail { - Author = Mapper.Map(author), + Author = (await _dataResolve.GetUserAsync(messageDeleted.AuthorId.ToUlong()))!, Content = messageDeleted.Content, Embeds = messageDeleted.Embeds, MessageCreatedAt = messageDeleted.MessageCreatedAt.ToLocalTime() diff --git a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs index 753e9a6b..0ced06fc 100644 --- a/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs +++ b/src/GrillBot.App/Actions/Api/V1/AuditLog/GetAuditLogList.cs @@ -1,7 +1,6 @@ using System.Text.Json; -using AutoMapper; using GrillBot.App.Helpers; -using GrillBot.Common.Extensions.Discord; +using GrillBot.App.Managers.DataResolve; using GrillBot.Common.FileStorage; using GrillBot.Common.Models; using GrillBot.Core.Extensions; @@ -12,7 +11,6 @@ using GrillBot.Core.Services.AuditLog.Models.Request.Search; using GrillBot.Data.Models.API.AuditLog; using GrillBot.Data.Models.API.AuditLog.Preview; -using GrillBot.Database.Services.Repository; using Microsoft.AspNetCore.Mvc; using File = GrillBot.Data.Models.API.AuditLog.File; using SearchModels = GrillBot.Core.Services.AuditLog.Models.Response.Search; @@ -21,27 +19,20 @@ namespace GrillBot.App.Actions.Api.V1.AuditLog; public class GetAuditLogList : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } private IAuditLogServiceClient AuditLogServiceClient { get; } - private IDiscordClient DiscordClient { get; } private BlobManagerFactoryHelper BlobManagerFactoryHelper { get; } - private Dictionary CachedUsers { get; } = new(); - private Dictionary CachedGuilds { get; } = new(); - private Dictionary> CachedChannels { get; } = new(); // Dictionary> + private readonly DataResolveManager _dataResolveManager; private BlobManager BlobManager { get; set; } = null!; private BlobManager LegacyBlobManager { get; set; } = null!; - public GetAuditLogList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper, IAuditLogServiceClient auditLogServiceClient, IDiscordClient discordClient, - BlobManagerFactoryHelper blobManagerFactoryHelper) : base(apiContext) + public GetAuditLogList(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, BlobManagerFactoryHelper blobManagerFactoryHelper, + DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; - Mapper = mapper; AuditLogServiceClient = auditLogServiceClient; - DiscordClient = discordClient; BlobManagerFactoryHelper = blobManagerFactoryHelper; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -61,11 +52,7 @@ public override async Task ProcessAsync() LegacyBlobManager = await BlobManagerFactoryHelper.CreateLegacyAsync(); } - await using var repository = DatabaseBuilder.CreateRepository(); - var result = await PaginatedResponse.CopyAndMapAsync( - response.Response!, - async entity => await MapListItemAsync(repository, entity) - ); + var result = await PaginatedResponse.CopyAndMapAsync(response.Response!, MapListItemAsync); return ApiResult.Ok(result); } @@ -95,7 +82,7 @@ private static AggregateException CreateValidationExceptions(ValidationProblemDe return new AggregateException(exceptions.ToArray()); } - private async Task MapListItemAsync(GrillBotRepository repository, SearchModels.LogListItem item) + private async Task MapListItemAsync(SearchModels.LogListItem item) { var result = new LogListItem { @@ -108,32 +95,20 @@ private async Task MapListItemAsync(GrillBotRepository repository, if (!string.IsNullOrEmpty(item.GuildId)) { - var guild = await ResolveGuildAsync(repository, item.GuildId); - if (guild is not null) - { - result.Guild = Mapper.Map(guild); + result.Guild = await _dataResolveManager.GetGuildAsync(item.GuildId.ToUlong()); - if (!string.IsNullOrEmpty(item.ChannelId)) - { - var channel = await ResolveChannelAsync(repository, item.GuildId, item.ChannelId); - if (channel is not null) - result.Channel = Mapper.Map(channel); - } - } + if (result.Guild is not null && !string.IsNullOrEmpty(item.ChannelId)) + result.Channel = await _dataResolveManager.GetChannelAsync(item.GuildId.ToUlong(), item.ChannelId.ToUlong()); } if (!string.IsNullOrEmpty(item.UserId)) - { - var user = await ResolveUserAsync(repository, item.UserId); - if (user is not null) - result.User = Mapper.Map(user); - } + result.User = await _dataResolveManager.GetUserAsync(item.UserId.ToUlong()); - result.Preview = await MapPreviewAsync(repository, item); + result.Preview = await MapPreviewAsync(item); return result; } - private async Task MapPreviewAsync(GrillBotRepository repository, SearchModels.LogListItem item) + private async Task MapPreviewAsync(SearchModels.LogListItem item) { if (item.Preview is not JsonElement jsonElement) return null; @@ -143,6 +118,7 @@ private async Task MapListItemAsync(GrillBotRepository repository, PropertyNamingPolicy = JsonNamingPolicy.CamelCase, DictionaryKeyPolicy = JsonNamingPolicy.CamelCase }; + switch (item.Type) { case LogType.Info or LogType.Warning or LogType.Error: @@ -156,47 +132,47 @@ private async Task MapListItemAsync(GrillBotRepository repository, case LogType.OverwriteCreated or LogType.OverwriteDeleted: { var preview = jsonElement.Deserialize(options)!; - var role = preview.TargetType == PermissionTarget.Role ? await DiscordClient.FindRoleAsync(preview.TargetId.ToUlong()) : null; - var user = preview.TargetType == PermissionTarget.User ? await ResolveUserAsync(repository, preview.TargetId) : null; - - return new OverwritePreview + var previewData = new OverwritePreview { - Role = Mapper.Map(role), - User = Mapper.Map(user), Allow = preview.Allow, Deny = preview.Deny }; + + if (preview.TargetType is PermissionTarget.Role) + previewData.Role = await _dataResolveManager.GetRoleAsync(preview.TargetId.ToUlong()); + else if (preview.TargetType is PermissionTarget.User) + previewData.User = await _dataResolveManager.GetUserAsync(preview.TargetId.ToUlong()); + + return previewData; } case LogType.OverwriteUpdated: { var preview = jsonElement.Deserialize(options)!; - var role = preview.TargetType == PermissionTarget.Role ? await DiscordClient.FindRoleAsync(preview.TargetId.ToUlong()) : null; - var user = preview.TargetType == PermissionTarget.User ? await ResolveUserAsync(repository, preview.TargetId) : null; + var previewData = new OverwriteUpdatedPreview(); - return new OverwriteUpdatedPreview - { - Role = Mapper.Map(role), - User = Mapper.Map(user), - }; + if (preview.TargetType is PermissionTarget.Role) + previewData.Role = await _dataResolveManager.GetRoleAsync(preview.TargetId.ToUlong()); + else if (preview.TargetType is PermissionTarget.User) + previewData.User = await _dataResolveManager.GetUserAsync(preview.TargetId.ToUlong()); + + return previewData; } case LogType.Unban: { var preview = jsonElement.Deserialize(options)!; - var user = await ResolveUserAsync(repository, preview.UserId); return new UnbanPreview { - User = Mapper.Map(user) + User = (await _dataResolveManager.GetUserAsync(preview.UserId.ToUlong()))! }; } case LogType.MemberUpdated: { var preview = jsonElement.Deserialize(options)!; - var user = await ResolveUserAsync(repository, preview.UserId); return new MemberUpdatedPreview { - User = Mapper.Map(user), + User = (await _dataResolveManager.GetUserAsync(preview.UserId.ToUlong()))!, SelfUnverifyMinimalTimeChange = preview.SelfUnverifyMinimalTimeChange, FlagsChanged = preview.FlagsChanged, NicknameChanged = preview.NicknameChanged, @@ -206,12 +182,11 @@ private async Task MapListItemAsync(GrillBotRepository repository, case LogType.MemberRoleUpdated: { var preview = jsonElement.Deserialize(options)!; - var user = await ResolveUserAsync(repository, preview.UserId); return new MemberRoleUpdatedPreview { ModifiedRoles = preview.ModifiedRoles, - User = Mapper.Map(user) + User = (await _dataResolveManager.GetUserAsync(preview.UserId.ToUlong()))! }; } case LogType.GuildUpdated: @@ -219,11 +194,10 @@ private async Task MapListItemAsync(GrillBotRepository repository, case LogType.UserLeft: { var preview = jsonElement.Deserialize(options)!; - var user = await ResolveUserAsync(repository, preview.UserId); return new UserLeftPreview { - User = Mapper.Map(user), + User = (await _dataResolveManager.GetUserAsync(preview.UserId.ToUlong()))!, BanReason = preview.BanReason, IsBan = preview.IsBan, MemberCount = preview.MemberCount @@ -236,11 +210,10 @@ private async Task MapListItemAsync(GrillBotRepository repository, case LogType.MessageDeleted: { var preview = jsonElement.Deserialize(options)!; - var user = await ResolveUserAsync(repository, preview.AuthorId); return new MessageDeletedPreview { - User = Mapper.Map(user), + User = (await _dataResolveManager.GetUserAsync(preview.AuthorId.ToUlong()))!, Content = preview.Content, Embeds = preview.Embeds, MessageCreatedAt = preview.MessageCreatedAt.ToLocalTime() @@ -277,46 +250,4 @@ private File ConvertFile(SearchModels.File file, SearchModels.LogListItem item) Size = file.Size }; } - - private async Task ResolveUserAsync(GrillBotRepository repository, string userId) - { - if (CachedUsers.TryGetValue(userId, out var user)) - return user; - - user = await repository.User.FindUserByIdAsync(userId.ToUlong(), disableTracking: true); - if (user is null) - return null; - - CachedUsers.Add(user.Id, user); - return user; - } - - private async Task ResolveGuildAsync(GrillBotRepository repository, string guildId) - { - if (CachedGuilds.TryGetValue(guildId, out var guild)) - return guild; - - guild = await repository.Guild.FindGuildByIdAsync(guildId.ToUlong(), true); - if (guild is null) - return null; - - CachedGuilds.Add(guild.Id, guild); - return guild; - } - - private async Task ResolveChannelAsync(GrillBotRepository repository, string guildId, string channelId) - { - if (CachedChannels.TryGetValue(guildId, out var guildChannels) && guildChannels.TryGetValue(channelId, out var guildChannel)) - return guildChannel; - - guildChannel = await repository.Channel.FindChannelByIdAsync(channelId.ToUlong(), guildId.ToUlong(), true, includeDeleted: true); - if (guildChannel is null) - return null; - - if (!CachedChannels.ContainsKey(guildId)) - CachedChannels.Add(guildId, new Dictionary()); - - CachedChannels[guildId].Add(channelId, guildChannel); - return guildChannel; - } } diff --git a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetUserMeasuresDashboard.cs b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetUserMeasuresDashboard.cs index 8030cecc..353538da 100644 --- a/src/GrillBot.App/Actions/Api/V1/Dashboard/GetUserMeasuresDashboard.cs +++ b/src/GrillBot.App/Actions/Api/V1/Dashboard/GetUserMeasuresDashboard.cs @@ -1,5 +1,7 @@ -using GrillBot.Common.Managers.Localization; +using GrillBot.App.Managers.DataResolve; +using GrillBot.Common.Managers.Localization; using GrillBot.Common.Models; +using GrillBot.Core.Extensions; using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog.Models.Response.Info.Dashboard; using GrillBot.Core.Services.UserMeasures; @@ -9,51 +11,54 @@ namespace GrillBot.App.Actions.Api.V1.Dashboard; public class GetUserMeasuresDashboard : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } private ITextsManager Texts { get; } private IUserMeasuresServiceClient UserMeasuresService { get; } - public GetUserMeasuresDashboard(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, ITextsManager texts, - IUserMeasuresServiceClient userMeasuresService) : base(apiContext) + private readonly DataResolveManager _dataResolveManager; + + public GetUserMeasuresDashboard(ApiRequestContext apiContext, ITextsManager texts, IUserMeasuresServiceClient userMeasuresService, DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; Texts = texts; UserMeasuresService = userMeasuresService; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() { var data = await UserMeasuresService.GetDashboardDataAsync(); - var result = await MapAsync(data); + var result = await MapAsync(data).ToListAsync(); return ApiResult.Ok(result); } - private async Task> MapAsync(List rows) + private async IAsyncEnumerable MapAsync(List rows) { - var userIds = rows.Select(o => o.UserId).Distinct().ToList(); - var users = await GetUsersAsync(userIds); - - return rows.ConvertAll(o => new DashboardInfoRow + foreach (var row in rows) { - Name = FormatUser(users.Find(u => u.Id == o.UserId), o.UserId), - Result = GetText(o.Type) - }); - } + var user = await _dataResolveManager.GetUserAsync(row.UserId.ToUlong()); - private async Task> GetUsersAsync(List userIds) - { - await using var repository = DatabaseBuilder.CreateRepository(); - return await repository.User.GetUsersByIdsAsync(userIds); + yield return new DashboardInfoRow + { + Result = GetText(row.Type), + Name = FormatUser(user, row.UserId) + }; + } } private string GetText(string id) => Texts[$"User/UserMeasures/{id}", ApiContext.Language]; - private string FormatUser(Database.Entity.User? user, string defaultValue) + private string FormatUser(Data.Models.API.Users.User? user, string defaultValue) { - return user is null ? - string.Format(GetText("UnknownUser"), defaultValue) : - $"User({user.Id}/{user.GetDisplayName()})"; + if (user is null) + return string.Format(GetText("UnknownUser"), defaultValue); + + var entity = new Database.Entity.User + { + Username = user.Username, + GlobalAlias = user.GlobalAlias, + }; + + return $"User({user.Id}/{entity.GetDisplayName()})"; } } diff --git a/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs b/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs index 493fef45..ac3a8079 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/ComputeUserPoints.cs @@ -1,13 +1,12 @@ -using AutoMapper; -using GrillBot.Common.Extensions.Discord; +using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.PointsService.Models; using GrillBot.Core.Extensions; using GrillBot.Data.Models.API.Users; using GrillBot.Database.Enums.Internal; -using GrillBot.Database.Services.Repository; using GrillBot.Core.Infrastructure.Actions; +using GrillBot.App.Managers.DataResolve; namespace GrillBot.App.Actions.Api.V1.Points; @@ -15,16 +14,17 @@ public class ComputeUserPoints : ApiAction { private GrillBotDatabaseBuilder DatabaseBuilder { get; } private IDiscordClient DiscordClient { get; } - private IMapper Mapper { get; } private IPointsServiceClient PointsServiceClient { get; } - public ComputeUserPoints(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IDiscordClient discordClient, IMapper mapper, - IPointsServiceClient pointsServiceClient) : base(apiContext) + private readonly DataResolveManager _dataResolveManager; + + public ComputeUserPoints(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IDiscordClient discordClient, IPointsServiceClient pointsServiceClient, + DataResolveManager dataResolveManager) : base(apiContext) { DatabaseBuilder = databaseBuilder; DiscordClient = discordClient; - Mapper = mapper; PointsServiceClient = pointsServiceClient; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -33,24 +33,24 @@ public override async Task ProcessAsync() userId ??= ApiContext.GetUserId(); var result = new List(); - await using var repository = DatabaseBuilder.CreateRepository(); - foreach (var guildId in await GetGuildIdsAsync(userId.Value)) { var status = await PointsServiceClient.GetStatusOfPointsAsync(guildId.ToString(), userId.Value.ToString()); - result.Add(await TransformStatusAsync(repository, guildId, userId.Value, status)); + result.Add(await TransformStatusAsync(guildId, userId.Value, status)); } return ApiResult.Ok(result); } - private async Task TransformStatusAsync(GrillBotRepository repository, ulong guildId, ulong userId, PointsStatus status) + private async Task TransformStatusAsync(ulong guildId, ulong userId, PointsStatus status) { + var guild = await _dataResolveManager.GetGuildAsync(guildId); + var user = await _dataResolveManager.GetUserAsync(userId); + return new UserPointsItem { - Guild = await TransformGuildAsync(repository, guildId), - User = await TransformUserAsync(repository, userId), - + Guild = guild!, + User = user!, PointsToday = status.Today, TotalPoints = status.Total, PointsMonthBack = status.MonthBack, @@ -58,18 +58,6 @@ private async Task TransformStatusAsync(GrillBotRepository repos }; } - private async Task TransformGuildAsync(GrillBotRepository repository, ulong guildId) - { - var dbGuild = (await repository.Guild.FindGuildByIdAsync(guildId, true))!; - return Mapper.Map(dbGuild); - } - - private async Task TransformUserAsync(GrillBotRepository repository, ulong userId) - { - var user = await repository.User.FindUserByIdAsync(userId, UserIncludeOptions.None, true); - return Mapper.Map(user); - } - private async Task> GetGuildIdsAsync(ulong userId) { var ids = new List(); diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs index aac87ea3..c30de0a1 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetPointsLeaderboard.cs @@ -1,11 +1,11 @@ -using AutoMapper; -using GrillBot.Common.Extensions.Discord; +using GrillBot.Common.Extensions.Discord; using GrillBot.Common.Models; using GrillBot.Core.Services.PointsService; using GrillBot.Core.Extensions; using GrillBot.Data.Models.API.Users; using GrillBot.Core.Services.PointsService.Enums; using GrillBot.Core.Infrastructure.Actions; +using GrillBot.App.Managers.DataResolve; namespace GrillBot.App.Actions.Api.V1.Points; @@ -13,16 +13,17 @@ public class GetPointsLeaderboard : ApiAction { private IDiscordClient DiscordClient { get; } private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } private IPointsServiceClient PointsServiceClient { get; } - public GetPointsLeaderboard(ApiRequestContext apiContext, IDiscordClient discordClient, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper, - IPointsServiceClient pointsServiceClient) : base(apiContext) + private readonly DataResolveManager _dataResolveManager; + + public GetPointsLeaderboard(ApiRequestContext apiContext, IDiscordClient discordClient, GrillBotDatabaseBuilder databaseBuilder, IPointsServiceClient pointsServiceClient, + DataResolveManager dataResolveManager) : base(apiContext) { DiscordClient = discordClient; DatabaseBuilder = databaseBuilder; - Mapper = mapper; PointsServiceClient = pointsServiceClient; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -33,25 +34,26 @@ public override async Task ProcessAsync() const LeaderboardColumnFlag leaderboardColumns = LeaderboardColumnFlag.YearBack | LeaderboardColumnFlag.MonthBack | LeaderboardColumnFlag.Today | LeaderboardColumnFlag.Total; const LeaderboardSortOptions leaderboardSort = LeaderboardSortOptions.ByTotalDescending; - foreach (var guild in await DiscordClient.FindMutualGuildsAsync(ApiContext.GetUserId())) + var mutualGuilds = await DiscordClient.FindMutualGuildsAsync(ApiContext.GetUserId()); + foreach (var guildId in mutualGuilds.Select(o => o.Id)) { - var leaderboard = await PointsServiceClient.GetLeaderboardAsync(guild.Id.ToString(), 0, 0, leaderboardColumns, leaderboardSort); + var leaderboard = await PointsServiceClient.GetLeaderboardAsync(guildId.ToString(), 0, 0, leaderboardColumns, leaderboardSort); leaderboard.ValidationErrors.AggregateAndThrow(); - var guildData = Mapper.Map(await repository.Guild.FindGuildAsync(guild, true)); - var nicknames = await repository.GuildUser.GetUserNicknamesAsync(guild.Id); - var usersList = await repository.User.GetUsersByIdsAsync(leaderboard.Response!.Select(o => o.UserId).Distinct().ToList()); - var users = usersList.ToDictionary(o => o.Id, o => o); + var guildData = (await _dataResolveManager.GetGuildAsync(guildId))!; + var nicknames = await repository.GuildUser.GetUserNicknamesAsync(guildId); foreach (var item in leaderboard.Response!) { - if (!users.TryGetValue(item.UserId, out var user)) continue; + var user = await _dataResolveManager.GetUserAsync(item.UserId.ToUlong()); + if (user is null) + continue; result.Add(new UserPointsItem { Guild = guildData, Nickname = nicknames.TryGetValue(item.UserId, out var nickname) ? nickname : null, - User = Mapper.Map(user), + User = user, PointsToday = item.Today, TotalPoints = item.Total, PointsMonthBack = item.MonthBack, diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs index c57a50dc..9ceccf39 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetTransactionList.cs @@ -1,4 +1,4 @@ -using AutoMapper; +using GrillBot.App.Managers.DataResolve; using GrillBot.Common.Models; using GrillBot.Core.Extensions; using GrillBot.Core.Infrastructure.Actions; @@ -6,21 +6,19 @@ using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.PointsService.Models; using GrillBot.Data.Models.API.Points; -using GrillBot.Database.Enums.Internal; namespace GrillBot.App.Actions.Api.V1.Points; public class GetTransactionList : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } private IPointsServiceClient PointsServiceClient { get; } - public GetTransactionList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper, IPointsServiceClient pointsServiceClient) : base(apiContext) + private readonly DataResolveManager _dataResolveManager; + + public GetTransactionList(ApiRequestContext apiContext, IPointsServiceClient pointsServiceClient, DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; - Mapper = mapper; PointsServiceClient = pointsServiceClient; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -30,26 +28,10 @@ public override async Task ProcessAsync() var transactions = await PointsServiceClient.GetTransactionListAsync(request); transactions.ValidationErrors.AggregateAndThrow(); - var guildCache = new Dictionary(); - var userCache = new Dictionary(); - - await using var repository = DatabaseBuilder.CreateRepository(); - var result = await PaginatedResponse.CopyAndMapAsync(transactions.Response!, async entity => { - if (!guildCache.TryGetValue(entity.GuildId, out var guild)) - { - var dbGuild = await repository.Guild.FindGuildByIdAsync(entity.GuildId.ToUlong(), true); - guild = Mapper.Map(dbGuild); - guildCache.Add(entity.GuildId, guild); - } - - if (!userCache.TryGetValue(entity.UserId, out var user)) - { - var dbUser = await repository.User.FindUserByIdAsync(entity.UserId.ToUlong(), UserIncludeOptions.None, true); - user = Mapper.Map(dbUser); - userCache.Add(entity.UserId, user); - } + var guild = await _dataResolveManager.GetGuildAsync(entity.GuildId.ToUlong()); + var user = await _dataResolveManager.GetUserAsync(entity.UserId.ToUlong()); var mergeInfo = entity.MergedCount > 0 ? new PointsMergeInfo @@ -70,8 +52,8 @@ public override async Task ProcessAsync() ReactionId = entity.ReactionId, MessageId = entity.MessageId, MergeInfo = mergeInfo, - Guild = guild, - User = user + Guild = guild!, + User = user! }; }); diff --git a/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs b/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs index f4dc694e..77367304 100644 --- a/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs +++ b/src/GrillBot.App/Actions/Api/V1/Points/GetUserList.cs @@ -1,91 +1,46 @@ -using AutoMapper; +using GrillBot.App.Managers.DataResolve; using GrillBot.Common.Models; using GrillBot.Core.Extensions; using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Models.Pagination; using GrillBot.Core.Services.PointsService; using GrillBot.Core.Services.PointsService.Models.Users; -using GrillBot.Database.Enums.Internal; -using GrillBot.Database.Services.Repository; namespace GrillBot.App.Actions.Api.V1.Points; public class GetUserList : ApiAction { private IPointsServiceClient PointsServiceClient { get; } - private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } + private readonly DataResolveManager _dataResolveManager; - private Dictionary CachedGuilds { get; } = new(); - private Dictionary CachedUsers { get; } = new(); - - public GetUserList(ApiRequestContext apiContext, IPointsServiceClient pointsServiceClient, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper) : base(apiContext) + public GetUserList(ApiRequestContext apiContext, IPointsServiceClient pointsServiceClient, DataResolveManager dataResolveManager) : base(apiContext) { PointsServiceClient = pointsServiceClient; - DatabaseBuilder = databaseBuilder; - Mapper = mapper; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() { var request = (UserListRequest)Parameters[0]!; var userList = await PointsServiceClient.GetUserListAsync(request); - await using var repository = DatabaseBuilder.CreateRepository(); - - var userIds = userList.Data.Select(o => o.UserId).Distinct().ToList(); - var users = await repository.User.GetUsersByIdsAsync(userIds); - foreach (var user in users) - CachedUsers.Add(user.Id, Mapper.Map(user)); - - var guildIds = userList.Data.Select(o => o.GuildId).Distinct().ToList(); - var guilds = await repository.Guild.GetGuildsByIdsAsync(guildIds); - foreach (var guild in guilds) - CachedGuilds.Add(guild.Id, Mapper.Map(guild)); - - var result = await PaginatedResponse.CopyAndMapAsync(userList, entity => MapItemAsync(entity, repository)); + var result = await PaginatedResponse.CopyAndMapAsync(userList, MapItemAsync); return ApiResult.Ok(result); } - private async Task MapItemAsync(UserListItem item, GrillBotRepository repository) + private async Task MapItemAsync(UserListItem item) { + var guild = await _dataResolveManager.GetGuildAsync(item.GuildId.ToUlong()); + var user = await _dataResolveManager.GetUserAsync(item.UserId.ToUlong()); + return new Data.Models.API.Points.UserListItem { ActivePoints = item.ActivePoints, ExpiredPoints = item.ExpiredPoints, - Guild = await GetGuildAsync(repository, item.GuildId), - User = await GetUserAsync(repository, item.UserId), + Guild = guild!, + User = user!, MergedPoints = item.MergedPoints, PointsDeactivated = item.PointsDeactivated }; } - - private async Task GetGuildAsync(GrillBotRepository repository, string guildId) - { - if (CachedGuilds.TryGetValue(guildId, out var guild)) - return guild; - - var guildEntity = await repository.Guild.FindGuildByIdAsync(guildId.ToUlong(), true); - guild = Mapper.Map(guildEntity); - - CachedGuilds.Add(guildId, guild); - return guild; - } - - private async Task GetUserAsync(GrillBotRepository repository, string userId) - { - if (CachedUsers.TryGetValue(userId, out var user)) - return user; - - var userEntity = await repository.User.FindUserByIdAsync(userId.ToUlong(), UserIncludeOptions.None, true); - userEntity ??= new Database.Entity.User - { - Id = userId, - Username = userId - }; - - user = Mapper.Map(userEntity); - CachedUsers.Add(userId, user); - return user; - } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs index 559ad5b8..48a68a26 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetApiUserStatistics.cs @@ -1,4 +1,5 @@ -using GrillBot.Common.Models; +using GrillBot.App.Managers.DataResolve; +using GrillBot.Common.Models; using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Data.Models.API.Statistics; @@ -7,36 +8,41 @@ namespace GrillBot.App.Actions.Api.V1.Statistics; public class GetApiUserStatistics : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } private IAuditLogServiceClient AuditLogServiceClient { get; } + private readonly DataResolveManager _dataResolveManager; - public GetApiUserStatistics(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IAuditLogServiceClient auditLogServiceClient) : base(apiContext) + public GetApiUserStatistics(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; AuditLogServiceClient = auditLogServiceClient; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() { var criteria = (string)Parameters[0]!; var statistics = await AuditLogServiceClient.GetUserApiStatisticsAsync(criteria); - var userIds = statistics - .Select(o => ulong.TryParse(o.UserId, out _) ? o.UserId : "") - .Where(o => !string.IsNullOrEmpty(o)) - .Distinct() - .ToList(); + var result = new List(); - await using var repository = DatabaseBuilder.CreateRepository(); - var users = await repository.User.GetUsersByIdsAsync(userIds); - var usernames = users.ToDictionary(o => o.Id, o => o.Username); - - var result = statistics.Select(o => new UserActionCountItem + foreach (var item in statistics) { - Username = usernames.TryGetValue(o.UserId, out var username) ? username : o.UserId, - Count = o.Count, - Action = o.Action - }).OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); + result.Add(new UserActionCountItem + { + Username = await ResolveUsernameAsync(item.UserId), + Action = item.Action, + Count = item.Count + }); + } + result = result.OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); return ApiResult.Ok(result); } + + private async Task ResolveUsernameAsync(string userId) + { + if (!ulong.TryParse(userId, CultureInfo.InvariantCulture, out var id)) + return userId; + + var user = await _dataResolveManager.GetUserAsync(id); + return string.IsNullOrEmpty(user?.Username) ? userId : user.Username; + } } diff --git a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs index 01407815..7adde2da 100644 --- a/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs +++ b/src/GrillBot.App/Actions/Api/V1/Statistics/GetUserCommandStatistics.cs @@ -1,4 +1,5 @@ -using GrillBot.Common.Models; +using GrillBot.App.Managers.DataResolve; +using GrillBot.Common.Models; using GrillBot.Core.Infrastructure.Actions; using GrillBot.Core.Services.AuditLog; using GrillBot.Data.Models.API.Statistics; @@ -7,31 +8,40 @@ namespace GrillBot.App.Actions.Api.V1.Statistics; public class GetUserCommandStatistics : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } private IAuditLogServiceClient AuditLogServiceClient { get; } + private readonly DataResolveManager _dataResolveManager; - public GetUserCommandStatistics(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IAuditLogServiceClient auditLogServiceClient) : base(apiContext) + public GetUserCommandStatistics(ApiRequestContext apiContext, IAuditLogServiceClient auditLogServiceClient, DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; AuditLogServiceClient = auditLogServiceClient; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() { var statistics = await AuditLogServiceClient.GetUserCommandStatisticsAsync(); - var userIds = statistics.Select(o => o.UserId).Distinct().ToList(); + var result = new List(); - await using var repository = DatabaseBuilder.CreateRepository(); - var users = await repository.User.GetUsersByIdsAsync(userIds); - var usernames = users.ToDictionary(o => o.Id, o => o.Username); - - var result = statistics.Select(o => new UserActionCountItem + foreach (var item in statistics) { - Username = usernames.TryGetValue(o.UserId, out var username) ? username : o.UserId, - Action = o.Action, - Count = o.Count - }).OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); + result.Add(new UserActionCountItem + { + Action = item.Action, + Count = item.Count, + Username = await ResolveUsernameAsync(item.UserId) + }); + } + result = result.OrderBy(o => o.Username).ThenBy(o => o.Action).ToList(); return ApiResult.Ok(result); } + + private async Task ResolveUsernameAsync(string userId) + { + if (!ulong.TryParse(userId, CultureInfo.InvariantCulture, out var id)) + return userId; + + var user = await _dataResolveManager.GetUserAsync(id); + return string.IsNullOrEmpty(user?.Username) ? userId : user.Username; + } } diff --git a/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs b/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs index 09d32b4d..1d20c05c 100644 --- a/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs +++ b/src/GrillBot.App/Actions/Api/V1/User/GetUserDetail.cs @@ -8,12 +8,12 @@ using GrillBot.Core.Extensions; using GrillBot.Database.Enums.Internal; using GrillBot.Core.Infrastructure.Actions; -using GrillBot.Database.Services.Repository; using ApiModels = GrillBot.Data.Models.API; using Entity = GrillBot.Database.Entity; using GrillBot.Core.Services.UserMeasures; using GrillBot.Core.Services.UserMeasures.Models.MeasuresList; using GrillBot.Data.Enums; +using GrillBot.App.Managers.DataResolve; namespace GrillBot.App.Actions.Api.V1.User; @@ -25,9 +25,10 @@ public class GetUserDetail : ApiAction private ITextsManager Texts { get; } private IPointsServiceClient PointsServiceClient { get; } private IUserMeasuresServiceClient UserMeasuresService { get; } + private readonly DataResolveManager _dataResolveManager; public GetUserDetail(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper, IDiscordClient discordClient, ITextsManager texts, - IPointsServiceClient pointsServiceClient, IUserMeasuresServiceClient userMeasuresService) : base(apiContext) + IPointsServiceClient pointsServiceClient, IUserMeasuresServiceClient userMeasuresService, DataResolveManager dataResolveManager) : base(apiContext) { DatabaseBuilder = databaseBuilder; Mapper = mapper; @@ -35,6 +36,7 @@ public GetUserDetail(ApiRequestContext apiContext, GrillBotDatabaseBuilder datab Texts = texts; PointsServiceClient = pointsServiceClient; UserMeasuresService = userMeasuresService; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -71,7 +73,7 @@ public override async Task ProcessAsync() await AddDiscordDataAsync(result); foreach (var guild in entity.Guilds) - result.Guilds.Add(await CreateGuildDetailAsync(repository, guild)); + result.Guilds.Add(await CreateGuildDetailAsync(guild)); result.Guilds = result.Guilds.OrderByDescending(o => o.IsUserInGuild).ThenBy(o => o.Guild.Name).ToList(); return result; @@ -86,7 +88,7 @@ private async Task AddDiscordDataAsync(ApiModels.Users.UserDetail result) result.IsKnown = true; } - private async Task CreateGuildDetailAsync(GrillBotRepository repository, Entity.GuildUser guildUserEntity) + private async Task CreateGuildDetailAsync(Entity.GuildUser guildUserEntity) { var result = Mapper.Map(guildUserEntity); @@ -94,13 +96,13 @@ private async Task AddDiscordDataAsync(ApiModels.Users.UserDetail result) result.Channels = result.Channels.OrderByDescending(o => o.Count).ThenBy(o => o.Channel.Name).ToList(); result.Emotes = result.Emotes.OrderByDescending(o => o.UseCount).ThenByDescending(o => o.LastOccurence).ThenBy(o => o.Emote.Name).ToList(); - await UpdateGuildDetailAsync(repository, result, guildUserEntity); + await UpdateGuildDetailAsync(result, guildUserEntity); return result; } - private async Task UpdateGuildDetailAsync(GrillBotRepository repository, ApiModels.Users.GuildUserDetail detail, Entity.GuildUser entity) + private async Task UpdateGuildDetailAsync(ApiModels.Users.GuildUserDetail detail, Entity.GuildUser entity) { - await SetUserMeasuresAsync(repository, detail, entity); + await SetUserMeasuresAsync(detail, entity); detail.HavePointsTransaction = await PointsServiceClient.ExistsAnyTransactionAsync(entity.GuildId, entity.UserId); var guild = await DiscordClient.GetGuildAsync(detail.Guild.Id.ToUlong()); @@ -139,7 +141,7 @@ private async Task SetVisibleChannelsAsync(ApiModels.Users.GuildUserDetail detai .ToList(); } - private async Task SetUserMeasuresAsync(GrillBotRepository repository, ApiModels.Users.GuildUserDetail detail, Entity.GuildUser entity) + private async Task SetUserMeasuresAsync(ApiModels.Users.GuildUserDetail detail, Entity.GuildUser entity) { var parameters = new MeasuresListParams { @@ -155,19 +157,14 @@ private async Task SetUserMeasuresAsync(GrillBotRepository repository, ApiModels var measuresResult = await UserMeasuresService.GetMeasuresListAsync(parameters); measuresResult.ValidationErrors.AggregateAndThrow(); - var measures = measuresResult.Response!.Data; - - var moderatorIds = measures.Select(o => o.ModeratorId).Distinct().ToList(); - var moderators = await repository.User.GetUsersByIdsAsync(moderatorIds); - - foreach (var measure in measures) + foreach (var measure in measuresResult.Response!.Data) { - var moderator = moderators.Find(o => o.Id == measure.ModeratorId); + var moderator = await _dataResolveManager.GetUserAsync(measure.ModeratorId.ToUlong()); detail.UserMeasures.Add(new ApiModels.UserMeasures.UserMeasuresItem { CreatedAt = measure.CreatedAtUtc.ToLocalTime(), - Moderator = Mapper.Map(moderator), + Moderator = moderator!, ValidTo = measure.ValidTo?.ToLocalTime(), Type = measure.Type switch { diff --git a/src/GrillBot.App/Actions/Api/V1/UserMeasures/GetUserMeasuresList.cs b/src/GrillBot.App/Actions/Api/V1/UserMeasures/GetUserMeasuresList.cs index 4f013a94..d3cf3bb3 100644 --- a/src/GrillBot.App/Actions/Api/V1/UserMeasures/GetUserMeasuresList.cs +++ b/src/GrillBot.App/Actions/Api/V1/UserMeasures/GetUserMeasuresList.cs @@ -2,30 +2,23 @@ using GrillBot.Core.Infrastructure.Actions; using GrillBot.Data.Models.API.UserMeasures; using GrillBot.Core.Extensions; -using GrillBot.Database.Services.Repository; using GrillBot.Core.Models.Pagination; -using ApiModels = GrillBot.Data.Models.API; -using AutoMapper; using GrillBot.Core.Services.UserMeasures; using GrillBot.Core.Services.UserMeasures.Models.MeasuresList; using GrillBot.Data.Enums; +using GrillBot.App.Managers.DataResolve; namespace GrillBot.App.Actions.Api.V1.UserMeasures; public class GetUserMeasuresList : ApiAction { - private GrillBotDatabaseBuilder DatabaseBuilder { get; } - private IMapper Mapper { get; } private IUserMeasuresServiceClient UserMeasuresService { get; } + private readonly DataResolveManager _dataResolveManager; - private Dictionary CachedUsers { get; } = new(); - private Dictionary CachedGuilds { get; } = new(); - - public GetUserMeasuresList(ApiRequestContext apiContext, GrillBotDatabaseBuilder databaseBuilder, IMapper mapper, IUserMeasuresServiceClient userMeasuresService) : base(apiContext) + public GetUserMeasuresList(ApiRequestContext apiContext, IUserMeasuresServiceClient userMeasuresService, DataResolveManager dataResolveManager) : base(apiContext) { - DatabaseBuilder = databaseBuilder; - Mapper = mapper; UserMeasuresService = userMeasuresService; + _dataResolveManager = dataResolveManager; } public override async Task ProcessAsync() @@ -40,19 +33,21 @@ public override async Task ProcessAsync() var measures = await UserMeasuresService.GetMeasuresListAsync(parameters); measures.ValidationErrors.AggregateAndThrow(); - var result = await MapItemsAsync(measures.Response!); + var result = await PaginatedResponse.CopyAndMapAsync(measures.Response!, MapAsync); return ApiResult.Ok(result); } - private async Task> MapItemsAsync(PaginatedResponse response) + private async Task MapAsync(MeasuresItem entity) { - await using var repository = DatabaseBuilder.CreateRepository(); + var guild = await _dataResolveManager.GetGuildAsync(entity.GuildId.ToUlong()); + var moderator = await _dataResolveManager.GetUserAsync(entity.ModeratorId.ToUlong()); + var user = await _dataResolveManager.GetUserAsync(entity.UserId.ToUlong()); - return await PaginatedResponse.CopyAndMapAsync(response, async entity => new UserMeasuresListItem + return new UserMeasuresListItem { CreatedAt = entity.CreatedAtUtc.ToLocalTime(), - Guild = await ReadGuildAsync(repository, entity.GuildId), - Moderator = await ReadUserAsync(repository, entity.ModeratorId), + Guild = guild!, + Moderator = moderator!, Reason = entity.Reason, Type = entity.Type switch { @@ -61,32 +56,8 @@ private async Task> MapItemsAsync(Pagina "Unverify" => UserMeasuresType.Unverify, _ => 0 }, - User = await ReadUserAsync(repository, entity.UserId), + User = user!, ValidTo = entity.ValidTo - }); - } - - private async Task ReadGuildAsync(GrillBotRepository repository, string guildId) - { - if (CachedGuilds.TryGetValue(guildId, out var guild)) - return guild; - - var entity = await repository.Guild.FindGuildByIdAsync(guildId.ToUlong(), true); - guild = Mapper.Map(entity); - CachedGuilds.Add(guildId, guild); - - return guild; - } - - private async Task ReadUserAsync(GrillBotRepository repository, string userId) - { - if (CachedUsers.TryGetValue(userId, out var user)) - return user; - - var entity = await repository.User.FindUserByIdAsync(userId.ToUlong(), disableTracking: true); - user = Mapper.Map(entity); - CachedUsers.Add(userId, user); - - return user; + }; } } diff --git a/src/GrillBot.App/Actions/Commands/Points/PointsLeaderboard.cs b/src/GrillBot.App/Actions/Commands/Points/PointsLeaderboard.cs index 3dc2c3f9..8b522013 100644 --- a/src/GrillBot.App/Actions/Commands/Points/PointsLeaderboard.cs +++ b/src/GrillBot.App/Actions/Commands/Points/PointsLeaderboard.cs @@ -9,6 +9,7 @@ using GrillBot.Core.Extensions; using GrillBot.Core.Services.PointsService.Enums; using GrillBot.Database.Services.Repository; +using GrillBot.App.Managers.DataResolve; namespace GrillBot.App.Actions.Commands.Points; @@ -19,15 +20,15 @@ public class PointsLeaderboard : CommandAction private ITextsManager Texts { get; } private FormatHelper FormatHelper { get; } private IPointsServiceClient PointsServiceClient { get; } - private GrillBotDatabaseBuilder DatabaseBuilder { get; } + private readonly DataResolveManager _dataResolveManager; public PointsLeaderboard(ITextsManager texts, FormatHelper formatHelper, IPointsServiceClient pointsServiceClient, - GrillBotDatabaseBuilder databaseBuilder) + DataResolveManager dataResolveManager) { Texts = texts; FormatHelper = formatHelper; PointsServiceClient = pointsServiceClient; - DatabaseBuilder = databaseBuilder; + _dataResolveManager = dataResolveManager; } public async Task<(Embed embed, MessageComponent? paginationComponent)> ProcessAsync(int page, bool overAllTime) @@ -86,12 +87,10 @@ private async Task> FormatRowsAsync(IReadOnlyList board, { var result = new List(); - await using var repository = DatabaseBuilder.CreateRepository(); - for (var i = 0; i < board.Count; i++) { var item = board[i]; - var username = await ResolveUsernameAsync(item.UserId.ToUlong(), guild, repository); + var username = await ResolveUsernameAsync(item.UserId.ToUlong(), guild); result.Add(FormatRow(i, item, skip, username, overAllTime)); } @@ -99,14 +98,23 @@ private async Task> FormatRowsAsync(IReadOnlyList board, return result; } - private static async Task ResolveUsernameAsync(ulong userId, IGuild guild, GrillBotRepository repository) + private async Task ResolveUsernameAsync(ulong userId, IGuild guild) { - var guildUser = await guild.GetUserAsync(userId); - if (guildUser is not null) - return guildUser.GetDisplayName(); + var guildUser = await _dataResolveManager.GetGuildUserAsync(guild.Id, userId); + if (guildUser is null) + return $"UnknownUser {userId}"; + + var entity = new Database.Entity.GuildUser + { + Nickname = guildUser.Nickname, + User = new Database.Entity.User + { + Username = guildUser.Username, + GlobalAlias = guildUser.GlobalAlias + } + }; - var dbUser = await repository.User.FindUserByIdAsync(userId, disableTracking: true); - return dbUser?.GetDisplayName() ?? $"UnknownUser {userId}"; + return entity.DisplayName!; } private string FormatRow(int index, PointsStatus item, int skip, string username, bool overAllTime) diff --git a/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs index 42e40e0b..2ea073f6 100644 --- a/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs +++ b/src/GrillBot.App/Managers/DataResolve/ChannelResolver.cs @@ -18,7 +18,7 @@ public ChannelResolver(IDiscordClient discordClient, IMapper mapper, GrillBotDat var guild = await _discordClient.GetGuildAsync(guildId, CacheMode.CacheOnly); return guild is null ? null : await guild.GetChannelAsync(channelId, CacheMode.CacheOnly); }, - repo => repo.Channel.FindChannelByIdAsync(channelId, guildId, true) + repo => repo.Channel.FindChannelByIdAsync(channelId, guildId, true, includeDeleted: true) ); } } diff --git a/src/GrillBot.Data/Models/API/Users/GuildUser.cs b/src/GrillBot.Data/Models/API/Users/GuildUser.cs index 31ebc0ae..2f54275b 100644 --- a/src/GrillBot.Data/Models/API/Users/GuildUser.cs +++ b/src/GrillBot.Data/Models/API/Users/GuildUser.cs @@ -8,7 +8,7 @@ public class GuildUser : User /// /// Used invite /// - public Invites.Invite UsedInvite { get; set; } + public Invites.Invite? UsedInvite { get; set; } /// /// Points count @@ -28,5 +28,5 @@ public class GuildUser : User /// /// Nickname. /// - public string Nickname { get; set; } + public string? Nickname { get; set; } } diff --git a/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs b/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs index df86a8f2..06b65503 100644 --- a/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs +++ b/src/GrillBot.Data/Models/API/Users/UsersMappingProfile.cs @@ -23,6 +23,11 @@ public UsersMappingProfile() .ForMember(o => o.GlobalAlias, opt => opt.MapFrom(src => src.User!.GlobalAlias)) .ForMember(o => o.IsBot, opt => opt.MapFrom(src => src.User!.HaveFlags(UserFlags.NotUser))); + CreateMap() + .ForMember(dst => dst.AvatarUrl, opt => opt.MapFrom(src => src.GetUserAvatarUrl(128))) + .ForMember(dst => dst.GlobalAlias, opt => opt.MapFrom(src => src.GlobalName)) + .ForMember(dst => dst.UsedInvite, opt => opt.Ignore()); + CreateMap() .ForMember(o => o.Guild, opt => opt.MapFrom(src => src.GuildUser.Guild)) .ForMember(o => o.User, opt => opt.MapFrom(src => src.GuildUser.User))