From 6a73b3bdd6b428b5a2d881dc68b4ed79ee2be8d8 Mon Sep 17 00:00:00 2001 From: Ske Date: Sat, 26 Oct 2019 19:45:30 +0200 Subject: [PATCH] Refactor data stores, merging the Store classes --- .../Controllers/AccountController.cs | 8 +- PluralKit.API/Controllers/MemberController.cs | 22 +- .../Controllers/MessageController.cs | 8 +- PluralKit.API/Controllers/SystemController.cs | 32 +- PluralKit.API/Startup.cs | 7 +- PluralKit.API/TokenAuthService.cs | 8 +- PluralKit.Bot/Bot.cs | 5 +- PluralKit.Bot/CommandSystem/Context.cs | 16 +- PluralKit.Bot/Commands/APICommands.cs | 8 +- PluralKit.Bot/Commands/LinkCommands.cs | 16 +- PluralKit.Bot/Commands/MemberCommands.cs | 44 +- PluralKit.Bot/Commands/ModCommands.cs | 8 +- PluralKit.Bot/Commands/SwitchCommands.cs | 26 +- PluralKit.Bot/Commands/SystemCommands.cs | 58 +-- PluralKit.Bot/Services/EmbedService.cs | 43 +- .../Services/PeriodicStatCollector.cs | 20 +- PluralKit.Bot/Services/ProxyService.cs | 18 +- PluralKit.Core/DataFiles.cs | 41 +- PluralKit.Core/Stores.cs | 475 +++++++++++++----- PluralKit.Web/Pages/ViewSystem.cshtml.cs | 12 +- PluralKit.Web/Startup.cs | 3 +- 21 files changed, 540 insertions(+), 338 deletions(-) diff --git a/PluralKit.API/Controllers/AccountController.cs b/PluralKit.API/Controllers/AccountController.cs index f99f565e..591927da 100644 --- a/PluralKit.API/Controllers/AccountController.cs +++ b/PluralKit.API/Controllers/AccountController.cs @@ -8,17 +8,17 @@ namespace PluralKit.API.Controllers [Route("v1/a")] public class AccountController: ControllerBase { - private SystemStore _systems; + private IDataStore _data; - public AccountController(SystemStore systems) + public AccountController(IDataStore data) { - _systems = systems; + _data = data; } [HttpGet("{aid}")] public async Task> GetSystemByAccount(ulong aid) { - var system = await _systems.GetByAccount(aid); + var system = await _data.GetSystemByAccount(aid); if (system == null) return NotFound("Account not found."); return Ok(system); diff --git a/PluralKit.API/Controllers/MemberController.cs b/PluralKit.API/Controllers/MemberController.cs index f420b7f9..f5a82fba 100644 --- a/PluralKit.API/Controllers/MemberController.cs +++ b/PluralKit.API/Controllers/MemberController.cs @@ -9,13 +9,13 @@ namespace PluralKit.API.Controllers [Route("v1/m")] public class MemberController: ControllerBase { - private MemberStore _members; + private IDataStore _data; private DbConnectionFactory _conn; private TokenAuthService _auth; - public MemberController(MemberStore members, DbConnectionFactory conn, TokenAuthService auth) + public MemberController(IDataStore data, DbConnectionFactory conn, TokenAuthService auth) { - _members = members; + _data = data; _conn = conn; _auth = auth; } @@ -23,7 +23,7 @@ namespace PluralKit.API.Controllers [HttpGet("{hid}")] public async Task> GetMember(string hid) { - var member = await _members.GetByHid(hid); + var member = await _data.GetMemberByHid(hid); if (member == null) return NotFound("Member not found."); return Ok(member); @@ -39,7 +39,7 @@ namespace PluralKit.API.Controllers return BadRequest("Member name cannot be null."); // Enforce per-system member limit - var memberCount = await _members.MemberCount(system); + var memberCount = await _data.GetSystemMemberCount(system); if (memberCount >= Limits.MaxMemberCount) return BadRequest($"Member limit reached ({memberCount} / {Limits.MaxMemberCount})."); @@ -61,7 +61,7 @@ namespace PluralKit.API.Controllers if (newMember.Suffix != null && newMember.Suffix.Length > 1000) return BadRequest(); - var member = await _members.Create(system, newMember.Name); + var member = await _data.CreateMember(system, newMember.Name); member.Name = newMember.Name; member.DisplayName = newMember.DisplayName; @@ -72,7 +72,7 @@ namespace PluralKit.API.Controllers member.Description = newMember.Description; member.Prefix = newMember.Prefix; member.Suffix = newMember.Suffix; - await _members.Save(member); + await _data.SaveMember(member); return Ok(member); } @@ -81,7 +81,7 @@ namespace PluralKit.API.Controllers [RequiresSystem] public async Task> PatchMember(string hid, [FromBody] PKMember newMember) { - var member = await _members.GetByHid(hid); + var member = await _data.GetMemberByHid(hid); if (member == null) return NotFound("Member not found."); if (member.System != _auth.CurrentSystem.Id) return Unauthorized($"Member '{hid}' is not part of your system."); @@ -116,7 +116,7 @@ namespace PluralKit.API.Controllers member.Description = newMember.Description; member.Prefix = newMember.Prefix; member.Suffix = newMember.Suffix; - await _members.Save(member); + await _data.SaveMember(member); return Ok(member); } @@ -125,12 +125,12 @@ namespace PluralKit.API.Controllers [RequiresSystem] public async Task> DeleteMember(string hid) { - var member = await _members.GetByHid(hid); + var member = await _data.GetMemberByHid(hid); if (member == null) return NotFound("Member not found."); if (member.System != _auth.CurrentSystem.Id) return Unauthorized($"Member '{hid}' is not part of your system."); - await _members.Delete(member); + await _data.DeleteMember(member); return Ok(); } diff --git a/PluralKit.API/Controllers/MessageController.cs b/PluralKit.API/Controllers/MessageController.cs index 4f788cdb..14b1f6b1 100644 --- a/PluralKit.API/Controllers/MessageController.cs +++ b/PluralKit.API/Controllers/MessageController.cs @@ -21,17 +21,17 @@ namespace PluralKit.API.Controllers [Route("msg")] public class MessageController: ControllerBase { - private MessageStore _messages; + private IDataStore _data; - public MessageController(MessageStore messages) + public MessageController(IDataStore _data) { - _messages = messages; + this._data = _data; } [HttpGet("{mid}")] public async Task> GetMessage(ulong mid) { - var msg = await _messages.Get(mid); + var msg = await _data.GetMessage(mid); if (msg == null) return NotFound("Message not found."); return new MessageReturn diff --git a/PluralKit.API/Controllers/SystemController.cs b/PluralKit.API/Controllers/SystemController.cs index 24f4f6df..1503c3fa 100644 --- a/PluralKit.API/Controllers/SystemController.cs +++ b/PluralKit.API/Controllers/SystemController.cs @@ -31,17 +31,13 @@ namespace PluralKit.API.Controllers [Route("v1/s")] public class SystemController : ControllerBase { - private SystemStore _systems; - private MemberStore _members; - private SwitchStore _switches; + private IDataStore _data; private DbConnectionFactory _conn; private TokenAuthService _auth; - public SystemController(SystemStore systems, MemberStore members, SwitchStore switches, DbConnectionFactory conn, TokenAuthService auth) + public SystemController(IDataStore data, DbConnectionFactory conn, TokenAuthService auth) { - _systems = systems; - _members = members; - _switches = switches; + _data = data; _conn = conn; _auth = auth; } @@ -56,7 +52,7 @@ namespace PluralKit.API.Controllers [HttpGet("{hid}")] public async Task> GetSystem(string hid) { - var system = await _systems.GetByHid(hid); + var system = await _data.GetSystemByHid(hid); if (system == null) return NotFound("System not found."); return Ok(system); } @@ -64,10 +60,10 @@ namespace PluralKit.API.Controllers [HttpGet("{hid}/members")] public async Task>> GetMembers(string hid) { - var system = await _systems.GetByHid(hid); + var system = await _data.GetSystemByHid(hid); if (system == null) return NotFound("System not found."); - var members = await _members.GetBySystem(system); + var members = await _data.GetSystemMembers(system); return Ok(members); } @@ -76,7 +72,7 @@ namespace PluralKit.API.Controllers { if (before == null) before = SystemClock.Instance.GetCurrentInstant(); - var system = await _systems.GetByHid(hid); + var system = await _data.GetSystemByHid(hid); if (system == null) return NotFound("System not found."); using (var conn = await _conn.Obtain()) @@ -96,13 +92,13 @@ namespace PluralKit.API.Controllers [HttpGet("{hid}/fronters")] public async Task> GetFronters(string hid) { - var system = await _systems.GetByHid(hid); + var system = await _data.GetSystemByHid(hid); if (system == null) return NotFound("System not found."); - var sw = await _switches.GetLatestSwitch(system); + var sw = await _data.GetLatestSwitch(system); if (sw == null) return NotFound("System has no registered switches."); - var members = await _switches.GetSwitchMembers(sw); + var members = await _data.GetSwitchMembers(sw); return Ok(new FrontersReturn { Timestamp = sw.Timestamp, @@ -130,7 +126,7 @@ namespace PluralKit.API.Controllers system.AvatarUrl = newSystem.AvatarUrl; system.UiTz = newSystem.UiTz ?? "UTC"; - await _systems.Save(system); + await _data.SaveSystem(system); return Ok(system); } @@ -142,10 +138,10 @@ namespace PluralKit.API.Controllers return BadRequest("Duplicate members in member list."); // We get the current switch, if it exists - var latestSwitch = await _switches.GetLatestSwitch(_auth.CurrentSystem); + var latestSwitch = await _data.GetLatestSwitch(_auth.CurrentSystem); if (latestSwitch != null) { - var latestSwitchMembers = await _switches.GetSwitchMembers(latestSwitch); + var latestSwitchMembers = await _data.GetSwitchMembers(latestSwitch); // Bail if this switch is identical to the latest one if (latestSwitchMembers.Select(m => m.Hid).SequenceEqual(param.Members)) @@ -174,7 +170,7 @@ namespace PluralKit.API.Controllers } // Finally, log the switch (yay!) - await _switches.RegisterSwitch(_auth.CurrentSystem, membersInOrder); + await _data.AddSwitch(_auth.CurrentSystem, membersInOrder); return NoContent(); } } diff --git a/PluralKit.API/Startup.cs b/PluralKit.API/Startup.cs index 17d9570a..afa3ff9a 100644 --- a/PluralKit.API/Startup.cs +++ b/PluralKit.API/Startup.cs @@ -25,11 +25,8 @@ namespace PluralKit.API .AddJsonOptions(opts => { opts.SerializerSettings.BuildSerializerSettings(); }); services - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient() - + .AddTransient() + .AddSingleton(svc => InitUtils.InitMetrics(svc.GetRequiredService(), "API")) .AddScoped() diff --git a/PluralKit.API/TokenAuthService.cs b/PluralKit.API/TokenAuthService.cs index 678ea8da..e0c5d277 100644 --- a/PluralKit.API/TokenAuthService.cs +++ b/PluralKit.API/TokenAuthService.cs @@ -8,11 +8,11 @@ namespace PluralKit.API { public PKSystem CurrentSystem { get; set; } - private SystemStore _systems; + private IDataStore _data; - public TokenAuthService(SystemStore systems) + public TokenAuthService(IDataStore data) { - _systems = systems; + _data = data; } public async Task InvokeAsync(HttpContext context, RequestDelegate next) @@ -20,7 +20,7 @@ namespace PluralKit.API var token = context.Request.Headers["Authorization"].FirstOrDefault(); if (token != null) { - CurrentSystem = await _systems.GetByToken(token); + CurrentSystem = await _data.GetSystemByToken(token); } await next.Invoke(context); diff --git a/PluralKit.Bot/Bot.cs b/PluralKit.Bot/Bot.cs index b2aa147b..ed5fd231 100644 --- a/PluralKit.Bot/Bot.cs +++ b/PluralKit.Bot/Bot.cs @@ -110,10 +110,7 @@ namespace PluralKit.Bot .AddTransient() .AddSingleton() - .AddTransient() - .AddTransient() - .AddTransient() - .AddTransient() + .AddTransient() .AddSingleton(svc => InitUtils.InitMetrics(svc.GetRequiredService())) .AddSingleton() diff --git a/PluralKit.Bot/CommandSystem/Context.cs b/PluralKit.Bot/CommandSystem/Context.cs index 7732d6b2..0e30c5d1 100644 --- a/PluralKit.Bot/CommandSystem/Context.cs +++ b/PluralKit.Bot/CommandSystem/Context.cs @@ -17,8 +17,7 @@ namespace PluralKit.Bot.CommandSystem private readonly SocketUserMessage _message; private readonly Parameters _parameters; - private readonly SystemStore _systems; - private readonly MemberStore _members; + private readonly IDataStore _data; private readonly PKSystem _senderSystem; private Command _currentCommand; @@ -28,8 +27,7 @@ namespace PluralKit.Bot.CommandSystem { _client = provider.GetRequiredService() as DiscordShardedClient; _message = message; - _systems = provider.GetRequiredService(); - _members = provider.GetRequiredService(); + _data = provider.GetRequiredService(); _senderSystem = senderSystem; _provider = provider; _parameters = new Parameters(message.Content.Substring(commandParseOffset)); @@ -86,7 +84,7 @@ namespace PluralKit.Bot.CommandSystem { await Reply($"{Emojis.Error} {e.Message}"); } - catch (TimeoutException e) + catch (TimeoutException) { // Got a complaint the old error was a bit too patronizing. Hopefully this is better? await Reply($"{Emojis.Error} Operation timed out, sorry. Try again, perhaps?"); @@ -121,10 +119,10 @@ namespace PluralKit.Bot.CommandSystem // Direct IDs and mentions are both handled by the below method: if (input.TryParseMention(out var id)) - return await _systems.GetByAccount(id); + return await _data.GetSystemByAccount(id); // Finally, try HID parsing - var system = await _systems.GetByHid(input); + var system = await _data.GetSystemByHid(input); return system; } @@ -138,11 +136,11 @@ namespace PluralKit.Bot.CommandSystem // - A textual name of a member *in your own system* // First, try member HID parsing: - if (await _members.GetByHid(input) is PKMember memberByHid) + if (await _data.GetMemberByHid(input) is PKMember memberByHid) return memberByHid; // Then, if we have a system, try finding by member name in system - if (_senderSystem != null && await _members.GetByName(_senderSystem, input) is PKMember memberByName) + if (_senderSystem != null && await _data.GetMemberByName(_senderSystem, input) is PKMember memberByName) return memberByName; // We didn't find anything, so we return null. diff --git a/PluralKit.Bot/Commands/APICommands.cs b/PluralKit.Bot/Commands/APICommands.cs index 65bff097..cd5d3f9d 100644 --- a/PluralKit.Bot/Commands/APICommands.cs +++ b/PluralKit.Bot/Commands/APICommands.cs @@ -7,10 +7,10 @@ namespace PluralKit.Bot.Commands { public class APICommands { - private SystemStore _systems; - public APICommands(SystemStore systems) + private IDataStore _data; + public APICommands(IDataStore data) { - _systems = systems; + _data = data; } public async Task GetToken(Context ctx) @@ -34,7 +34,7 @@ namespace PluralKit.Bot.Commands private async Task MakeAndSetNewToken(PKSystem system) { system.Token = PluralKit.Utils.GenerateToken(); - await _systems.Save(system); + await _data.SaveSystem(system); return system.Token; } diff --git a/PluralKit.Bot/Commands/LinkCommands.cs b/PluralKit.Bot/Commands/LinkCommands.cs index cd7b0f05..c77daf6e 100644 --- a/PluralKit.Bot/Commands/LinkCommands.cs +++ b/PluralKit.Bot/Commands/LinkCommands.cs @@ -8,11 +8,11 @@ namespace PluralKit.Bot.Commands { public class LinkCommands { - private SystemStore _systems; + private IDataStore _data; - public LinkCommands(SystemStore systems) + public LinkCommands(IDataStore data) { - _systems = systems; + _data = data; } public async Task LinkSystem(Context ctx) @@ -20,15 +20,15 @@ namespace PluralKit.Bot.Commands ctx.CheckSystem(); var account = await ctx.MatchUser() ?? throw new PKSyntaxError("You must pass an account to link with (either ID or @mention)."); - var accountIds = await _systems.GetLinkedAccountIds(ctx.System); + var accountIds = await _data.GetSystemAccounts(ctx.System); if (accountIds.Contains(account.Id)) throw Errors.AccountAlreadyLinked; - var existingAccount = await _systems.GetByAccount(account.Id); + var existingAccount = await _data.GetSystemByAccount(account.Id); if (existingAccount != null) throw Errors.AccountInOtherSystem(existingAccount); var msg = await ctx.Reply($"{account.Mention}, please confirm the link by clicking the {Emojis.Success} reaction on this message."); if (!await ctx.PromptYesNo(msg, user: account)) throw Errors.MemberLinkCancelled; - await _systems.Link(ctx.System, account.Id); + await _data.AddAccount(ctx.System, account.Id); await ctx.Reply($"{Emojis.Success} Account linked to system."); } @@ -42,7 +42,7 @@ namespace PluralKit.Bot.Commands else account = await ctx.MatchUser() ?? throw new PKSyntaxError("You must pass an account to link with (either ID or @mention)."); - var accountIds = (await _systems.GetLinkedAccountIds(ctx.System)).ToList(); + var accountIds = (await _data.GetSystemAccounts(ctx.System)).ToList(); if (!accountIds.Contains(account.Id)) throw Errors.AccountNotLinked; if (accountIds.Count == 1) throw Errors.UnlinkingLastAccount; @@ -50,7 +50,7 @@ namespace PluralKit.Bot.Commands $"Are you sure you want to unlink {account.Mention} from your system?"); if (!await ctx.PromptYesNo(msg)) throw Errors.MemberUnlinkCancelled; - await _systems.Unlink(ctx.System, account.Id); + await _data.RemoveAccount(ctx.System, account.Id); await ctx.Reply($"{Emojis.Success} Account unlinked."); } } diff --git a/PluralKit.Bot/Commands/MemberCommands.cs b/PluralKit.Bot/Commands/MemberCommands.cs index dbb80f59..ab7ca99d 100644 --- a/PluralKit.Bot/Commands/MemberCommands.cs +++ b/PluralKit.Bot/Commands/MemberCommands.cs @@ -11,16 +11,14 @@ namespace PluralKit.Bot.Commands { public class MemberCommands { - private SystemStore _systems; - private MemberStore _members; + private IDataStore _data; private EmbedService _embeds; private ProxyCacheService _proxyCache; - public MemberCommands(SystemStore systems, MemberStore members, EmbedService embeds, ProxyCacheService proxyCache) + public MemberCommands(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache) { - _systems = systems; - _members = members; + _data = data; _embeds = embeds; _proxyCache = proxyCache; } @@ -39,19 +37,19 @@ namespace PluralKit.Bot.Commands } // Warn if there's already a member by this name - var existingMember = await _members.GetByName(ctx.System, memberName); + var existingMember = await _data.GetMemberByName(ctx.System, memberName); if (existingMember != null) { var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?"); if (!await ctx.PromptYesNo(msg)) throw new PKError("Member creation cancelled."); } // Enforce per-system member limit - var memberCount = await _members.MemberCount(ctx.System); + var memberCount = await _data.GetSystemMemberCount(ctx.System); if (memberCount >= Limits.MaxMemberCount) throw Errors.MemberLimitReachedError; // Create the member - var member = await _members.Create(ctx.System, memberName); + var member = await _data.CreateMember(ctx.System, memberName); memberCount++; // Send confirmation and space hint @@ -83,7 +81,7 @@ namespace PluralKit.Bot.Commands } // Warn if there's already a member by this name - var existingMember = await _members.GetByName(ctx.System, newName); + var existingMember = await _data.GetMemberByName(ctx.System, newName); if (existingMember != null) { var msg = await ctx.Reply($"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.Name.SanitizeMentions()}\" (`{existingMember.Hid}`). Do you want to rename this member to that name too?"); if (!await ctx.PromptYesNo(msg)) throw new PKError("Member renaming cancelled."); @@ -91,7 +89,7 @@ namespace PluralKit.Bot.Commands // Rename the member target.Name = newName; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member renamed."); if (newName.Contains(" ")) await ctx.Reply($"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it."); @@ -108,7 +106,7 @@ namespace PluralKit.Bot.Commands if (description.IsLongerThan(Limits.MaxDescriptionLength)) throw Errors.DescriptionTooLongError(description.Length); target.Description = description; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member description {(description == null ? "cleared" : "changed")}."); } @@ -121,7 +119,7 @@ namespace PluralKit.Bot.Commands if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) throw Errors.MemberPronounsTooLongError(pronouns.Length); target.Pronouns = pronouns; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member pronouns {(pronouns == null ? "cleared" : "changed")}."); } @@ -139,7 +137,7 @@ namespace PluralKit.Bot.Commands } target.Color = color; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member color {(color == null ? "cleared" : "changed")}."); } @@ -158,7 +156,7 @@ namespace PluralKit.Bot.Commands } target.Birthday = date; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member birthdate {(date == null ? "cleared" : $"changed to {target.BirthdayString}")}."); } @@ -175,7 +173,7 @@ namespace PluralKit.Bot.Commands // Just reset and send OK message target.Prefix = null; target.Suffix = null; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member proxy tags cleared."); return; } @@ -188,7 +186,7 @@ namespace PluralKit.Bot.Commands // If the prefix/suffix is empty, use "null" instead (for DB) target.Prefix = prefixAndSuffix[0].Length > 0 ? prefixAndSuffix[0] : null; target.Suffix = prefixAndSuffix[1].Length > 0 ? prefixAndSuffix[1] : null; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member proxy tags changed to `{target.ProxyString.SanitizeMentions()}`. Try proxying now!"); await _proxyCache.InvalidateResultsForSystem(ctx.System); @@ -201,7 +199,7 @@ namespace PluralKit.Bot.Commands await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.Name.SanitizeMentions()}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__"); if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled; - await _members.Delete(target); + await _data.DeleteMember(target); await ctx.Reply($"{Emojis.Success} Member deleted."); await _proxyCache.InvalidateResultsForSystem(ctx.System); @@ -217,7 +215,7 @@ namespace PluralKit.Bot.Commands if (user.AvatarId == null) throw Errors.UserHasNoAvatar; target.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256); - await _members.Save(target); + await _data.SaveMember(target); var embed = new EmbedBuilder().WithImageUrl(target.AvatarUrl).Build(); await ctx.Reply( @@ -228,7 +226,7 @@ namespace PluralKit.Bot.Commands { await Utils.VerifyAvatarOrThrow(url); target.AvatarUrl = url; - await _members.Save(target); + await _data.SaveMember(target); var embed = new EmbedBuilder().WithImageUrl(url).Build(); await ctx.Reply($"{Emojis.Success} Member avatar changed.", embed: embed); @@ -237,14 +235,14 @@ namespace PluralKit.Bot.Commands { await Utils.VerifyAvatarOrThrow(attachment.Url); target.AvatarUrl = attachment.Url; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working."); } else { target.AvatarUrl = null; - await _members.Save(target); + await _data.SaveMember(target); await ctx.Reply($"{Emojis.Success} Member avatar cleared."); } @@ -262,7 +260,7 @@ namespace PluralKit.Bot.Commands throw Errors.DisplayNameTooLong(newDisplayName, ctx.System.MaxMemberNameLength); target.DisplayName = newDisplayName; - await _members.Save(target); + await _data.SaveMember(target); var successStr = $"{Emojis.Success} "; if (newDisplayName != null) @@ -288,7 +286,7 @@ namespace PluralKit.Bot.Commands public async Task ViewMember(Context ctx, PKMember target) { - var system = await _systems.GetById(target.System); + var system = await _data.GetSystemById(target.System); await ctx.Reply(embed: await _embeds.CreateMemberEmbed(system, target)); } } diff --git a/PluralKit.Bot/Commands/ModCommands.cs b/PluralKit.Bot/Commands/ModCommands.cs index df7c89fb..24355393 100644 --- a/PluralKit.Bot/Commands/ModCommands.cs +++ b/PluralKit.Bot/Commands/ModCommands.cs @@ -9,14 +9,14 @@ namespace PluralKit.Bot.Commands public class ModCommands { private LogChannelService _logChannels; - private MessageStore _messages; + private IDataStore _data; private EmbedService _embeds; - public ModCommands(LogChannelService logChannels, MessageStore messages, EmbedService embeds) + public ModCommands(LogChannelService logChannels, IDataStore data, EmbedService embeds) { _logChannels = logChannels; - _messages = messages; + _data = data; _embeds = embeds; } @@ -47,7 +47,7 @@ namespace PluralKit.Bot.Commands messageId = ulong.Parse(match.Groups[1].Value); else throw new PKSyntaxError($"Could not parse `{word}` as a message ID or link."); - var message = await _messages.Get(messageId); + var message = await _data.GetMessage(messageId); if (message == null) throw Errors.MessageNotFound(messageId); await ctx.Reply(embed: await _embeds.CreateMessageInfoEmbed(message)); diff --git a/PluralKit.Bot/Commands/SwitchCommands.cs b/PluralKit.Bot/Commands/SwitchCommands.cs index d026c733..49c5b4bf 100644 --- a/PluralKit.Bot/Commands/SwitchCommands.cs +++ b/PluralKit.Bot/Commands/SwitchCommands.cs @@ -12,11 +12,11 @@ namespace PluralKit.Bot.Commands { public class SwitchCommands { - private SwitchStore _switches; + private IDataStore _data; - public SwitchCommands(SwitchStore switches) + public SwitchCommands(IDataStore data) { - _switches = switches; + _data = data; } public async Task Switch(Context ctx) @@ -55,16 +55,16 @@ namespace PluralKit.Bot.Commands if (members.Select(m => m.Id).Distinct().Count() != members.Count) throw Errors.DuplicateSwitchMembers; // Find the last switch and its members if applicable - var lastSwitch = await _switches.GetLatestSwitch(ctx.System); + var lastSwitch = await _data.GetLatestSwitch(ctx.System); if (lastSwitch != null) { - var lastSwitchMembers = await _switches.GetSwitchMembers(lastSwitch); + var lastSwitchMembers = await _data.GetSwitchMembers(lastSwitch); // Make sure the requested switch isn't identical to the last one if (lastSwitchMembers.Select(m => m.Id).SequenceEqual(members.Select(m => m.Id))) throw Errors.SameSwitch(members); } - await _switches.RegisterSwitch(ctx.System, members); + await _data.AddSwitch(ctx.System, members); if (members.Count == 0) await ctx.Reply($"{Emojis.Success} Switch-out registered."); @@ -86,7 +86,7 @@ namespace PluralKit.Bot.Commands if (time.ToInstant() > SystemClock.Instance.GetCurrentInstant()) throw Errors.SwitchTimeInFuture; // Fetch the last two switches for the system to do bounds checking on - var lastTwoSwitches = (await _switches.GetSwitches(ctx.System, 2)).ToArray(); + var lastTwoSwitches = (await _data.GetSwitches(ctx.System, 2)).ToArray(); // If we don't have a switch to move, don't bother if (lastTwoSwitches.Length == 0) throw Errors.NoRegisteredSwitches; @@ -100,7 +100,7 @@ namespace PluralKit.Bot.Commands // Now we can actually do the move, yay! // But, we do a prompt to confirm. - var lastSwitchMembers = await _switches.GetSwitchMembers(lastTwoSwitches[0]); + var lastSwitchMembers = await _data.GetSwitchMembers(lastTwoSwitches[0]); var lastSwitchMemberStr = string.Join(", ", lastSwitchMembers.Select(m => m.Name)); var lastSwitchTimeStr = Formats.ZonedDateTimeFormat.Format(lastTwoSwitches[0].Timestamp.InZone(ctx.System.Zone)); var lastSwitchDeltaStr = Formats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp); @@ -112,7 +112,7 @@ namespace PluralKit.Bot.Commands if (!await ctx.PromptYesNo(msg)) throw Errors.SwitchMoveCancelled; // aaaand *now* we do the move - await _switches.MoveSwitch(lastTwoSwitches[0], time.ToInstant()); + await _data.MoveSwitch(lastTwoSwitches[0], time.ToInstant()); await ctx.Reply($"{Emojis.Success} Switch moved."); } @@ -121,10 +121,10 @@ namespace PluralKit.Bot.Commands ctx.CheckSystem(); // Fetch the last two switches for the system to do bounds checking on - var lastTwoSwitches = (await _switches.GetSwitches(ctx.System, 2)).ToArray(); + var lastTwoSwitches = (await _data.GetSwitches(ctx.System, 2)).ToArray(); if (lastTwoSwitches.Length == 0) throw Errors.NoRegisteredSwitches; - var lastSwitchMembers = await _switches.GetSwitchMembers(lastTwoSwitches[0]); + var lastSwitchMembers = await _data.GetSwitchMembers(lastTwoSwitches[0]); var lastSwitchMemberStr = string.Join(", ", lastSwitchMembers.Select(m => m.Name)); var lastSwitchDeltaStr = Formats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[0].Timestamp); @@ -136,7 +136,7 @@ namespace PluralKit.Bot.Commands } else { - var secondSwitchMembers = await _switches.GetSwitchMembers(lastTwoSwitches[1]); + var secondSwitchMembers = await _data.GetSwitchMembers(lastTwoSwitches[1]); var secondSwitchMemberStr = string.Join(", ", secondSwitchMembers.Select(m => m.Name)); var secondSwitchDeltaStr = Formats.DurationFormat.Format(SystemClock.Instance.GetCurrentInstant() - lastTwoSwitches[1].Timestamp); msg = await ctx.Reply( @@ -144,7 +144,7 @@ namespace PluralKit.Bot.Commands } if (!await ctx.PromptYesNo(msg)) throw Errors.SwitchDeleteCancelled; - await _switches.DeleteSwitch(lastTwoSwitches[0]); + await _data.DeleteSwitch(lastTwoSwitches[0]); await ctx.Reply($"{Emojis.Success} Switch deleted."); } diff --git a/PluralKit.Bot/Commands/SystemCommands.cs b/PluralKit.Bot/Commands/SystemCommands.cs index 0d109213..8a8f31a3 100644 --- a/PluralKit.Bot/Commands/SystemCommands.cs +++ b/PluralKit.Bot/Commands/SystemCommands.cs @@ -14,21 +14,16 @@ namespace PluralKit.Bot.Commands { public class SystemCommands { - private SystemStore _systems; - private MemberStore _members; - - private SwitchStore _switches; + private IDataStore _data; private EmbedService _embeds; private ProxyCacheService _proxyCache; - public SystemCommands(SystemStore systems, MemberStore members, SwitchStore switches, EmbedService embeds, ProxyCacheService proxyCache) + public SystemCommands(EmbedService embeds, ProxyCacheService proxyCache, IDataStore data) { - _systems = systems; - _members = members; - _switches = switches; _embeds = embeds; _proxyCache = proxyCache; + _data = data; } public async Task Query(Context ctx, PKSystem system) { @@ -41,8 +36,8 @@ namespace PluralKit.Bot.Commands { ctx.CheckNoSystem(); - var system = await _systems.Create(ctx.RemainderOrNull()); - await _systems.Link(system, ctx.Author.Id); + var system = await _data.CreateSystem(ctx.RemainderOrNull()); + await _data.AddAccount(system, ctx.Author.Id); await ctx.Reply($"{Emojis.Success} Your system has been created. Type `pk;system` to view it, and type `pk;help` for more information about commands you can use now."); } @@ -54,7 +49,7 @@ namespace PluralKit.Bot.Commands if (newSystemName != null && newSystemName.Length > Limits.MaxSystemNameLength) throw Errors.SystemNameTooLongError(newSystemName.Length); ctx.System.Name = newSystemName; - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); await ctx.Reply($"{Emojis.Success} System name {(newSystemName != null ? "changed" : "cleared")}."); } @@ -65,7 +60,7 @@ namespace PluralKit.Bot.Commands if (newDescription != null && newDescription.Length > Limits.MaxDescriptionLength) throw Errors.DescriptionTooLongError(newDescription.Length); ctx.System.Description = newDescription; - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); await ctx.Reply($"{Emojis.Success} System description {(newDescription != null ? "changed" : "cleared")}."); } @@ -80,17 +75,18 @@ namespace PluralKit.Bot.Commands { if (newTag.Length > Limits.MaxSystemTagLength) throw Errors.SystemNameTooLongError(newTag.Length); - // Check unproxyable messages *after* changing the tag (so it's seen in the method) but *before* we save to DB (so we can cancel) - var unproxyableMembers = await _members.GetUnproxyableMembers(ctx.System); - if (unproxyableMembers.Count > 0) - { - var msg = await ctx.Reply( - $"{Emojis.Warn} Changing your system tag to '{newTag.SanitizeMentions()}' will result in the following members being unproxyable, since the tag would bring their name over {Limits.MaxProxyNameLength} characters:\n**{string.Join(", ", unproxyableMembers.Select((m) => m.Name.SanitizeMentions()))}**\nDo you want to continue anyway?"); - if (!await ctx.PromptYesNo(msg)) throw new PKError("Tag change cancelled."); - } + // TODO: The proxy name limit is long enough now that this probably doesn't matter much. + // // Check unproxyable messages *after* changing the tag (so it's seen in the method) but *before* we save to DB (so we can cancel) + // var unproxyableMembers = await _data.GetUnproxyableMembers(ctx.System); + // if (unproxyableMembers.Count > 0) + // { + // var msg = await ctx.Reply( + // $"{Emojis.Warn} Changing your system tag to '{newTag.SanitizeMentions()}' will result in the following members being unproxyable, since the tag would bring their name over {Limits.MaxProxyNameLength} characters:\n**{string.Join(", ", unproxyableMembers.Select((m) => m.Name.SanitizeMentions()))}**\nDo you want to continue anyway?"); + // if (!await ctx.PromptYesNo(msg)) throw new PKError("Tag change cancelled."); + // } } - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); await ctx.Reply($"{Emojis.Success} System tag {(newTag != null ? "changed" : "cleared")}."); await _proxyCache.InvalidateResultsForSystem(ctx.System); @@ -105,7 +101,7 @@ namespace PluralKit.Bot.Commands { if (member.AvatarId == null) throw Errors.UserHasNoAvatar; ctx.System.AvatarUrl = member.GetAvatarUrl(ImageFormat.Png, size: 256); - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); var embed = new EmbedBuilder().WithImageUrl(ctx.System.AvatarUrl).Build(); await ctx.Reply( @@ -117,7 +113,7 @@ namespace PluralKit.Bot.Commands if (url != null) await ctx.BusyIndicator(() => Utils.VerifyAvatarOrThrow(url)); ctx.System.AvatarUrl = url; - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); var embed = url != null ? new EmbedBuilder().WithImageUrl(url).Build() : null; await ctx.Reply($"{Emojis.Success} System avatar {(url == null ? "cleared" : "changed")}.", embed: embed); @@ -133,7 +129,7 @@ namespace PluralKit.Bot.Commands var reply = await ctx.AwaitMessage(ctx.Channel, ctx.Author, timeout: TimeSpan.FromMinutes(1)); if (reply.Content != ctx.System.Hid) throw new PKError($"System deletion cancelled. Note that you must reply with your system ID (`{ctx.System.Hid}`) *verbatim*."); - await _systems.Delete(ctx.System); + await _data.DeleteSystem(ctx.System); await ctx.Reply($"{Emojis.Success} System deleted."); await _proxyCache.InvalidateResultsForSystem(ctx.System); @@ -142,7 +138,7 @@ namespace PluralKit.Bot.Commands public async Task MemberShortList(Context ctx, PKSystem system) { if (system == null) throw Errors.NoSystemError; - var members = await _members.GetBySystem(system); + var members = await _data.GetSystemMembers(system); var embedTitle = system.Name != null ? $"Members of {system.Name.SanitizeMentions()} (`{system.Hid}`)" : $"Members of `{system.Hid}`"; await ctx.Paginate( members.OrderBy(m => m.Name.ToLower()).ToList(), @@ -158,7 +154,7 @@ namespace PluralKit.Bot.Commands public async Task MemberLongList(Context ctx, PKSystem system) { if (system == null) throw Errors.NoSystemError; - var members = await _members.GetBySystem(system); + var members = await _data.GetSystemMembers(system); var embedTitle = system.Name != null ? $"Members of {system.Name} (`{system.Hid}`)" : $"Members of `{system.Hid}`"; await ctx.Paginate( members.OrderBy(m => m.Name.ToLower()).ToList(), @@ -181,7 +177,7 @@ namespace PluralKit.Bot.Commands { if (system == null) throw Errors.NoSystemError; - var sw = await _switches.GetLatestSwitch(system); + var sw = await _data.GetLatestSwitch(system); if (sw == null) throw Errors.NoRegisteredSwitches; await ctx.Reply(embed: await _embeds.CreateFronterEmbed(sw, system.Zone)); @@ -191,7 +187,7 @@ namespace PluralKit.Bot.Commands { if (system == null) throw Errors.NoSystemError; - var sws = (await _switches.GetSwitches(system, 10)).ToList(); + var sws = (await _data.GetSwitches(system, 10)).ToList(); if (sws.Count == 0) throw Errors.NoRegisteredSwitches; await ctx.Reply(embed: await _embeds.CreateFrontHistoryEmbed(sws, system.Zone)); @@ -209,7 +205,7 @@ namespace PluralKit.Bot.Commands if (rangeStart == null) throw Errors.InvalidDateTime(durationStr); if (rangeStart.Value.ToInstant() > now) throw Errors.FrontPercentTimeInFuture; - var frontpercent = await _switches.GetPerMemberSwitchDuration(system, rangeStart.Value.ToInstant(), now); + var frontpercent = await _data.GetFrontBreakdown(system, rangeStart.Value.ToInstant(), now); await ctx.Reply(embed: await _embeds.CreateFrontPercentEmbed(frontpercent, system.Zone)); } @@ -221,7 +217,7 @@ namespace PluralKit.Bot.Commands if (zoneStr == null) { ctx.System.UiTz = "UTC"; - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); await ctx.Reply($"{Emojis.Success} System time zone cleared."); return; } @@ -234,7 +230,7 @@ namespace PluralKit.Bot.Commands $"This will change the system time zone to {zone.Id}. The current time is {Formats.ZonedDateTimeFormat.Format(currentTime)}. Is this correct?"); if (!await ctx.PromptYesNo(msg)) throw Errors.TimezoneChangeCancelled; ctx.System.UiTz = zone.Id; - await _systems.Save(ctx.System); + await _data.SaveSystem(ctx.System); await ctx.Reply($"System time zone changed to {zone.Id}."); } diff --git a/PluralKit.Bot/Services/EmbedService.cs b/PluralKit.Bot/Services/EmbedService.cs index fe4438b1..a0136918 100644 --- a/PluralKit.Bot/Services/EmbedService.cs +++ b/PluralKit.Bot/Services/EmbedService.cs @@ -9,39 +9,34 @@ using Humanizer; using NodaTime; namespace PluralKit.Bot { - public class EmbedService { - private SystemStore _systems; - private MemberStore _members; - private SwitchStore _switches; - private MessageStore _messages; + public class EmbedService + { + private IDataStore _data; private IDiscordClient _client; - public EmbedService(SystemStore systems, MemberStore members, IDiscordClient client, SwitchStore switches, MessageStore messages) + public EmbedService(IDiscordClient client, IDataStore data) { - _systems = systems; - _members = members; _client = client; - _switches = switches; - _messages = messages; + _data = data; } public async Task CreateSystemEmbed(PKSystem system) { - var accounts = await _systems.GetLinkedAccountIds(system); + var accounts = await _data.GetSystemAccounts(system); // Fetch/render info for all accounts simultaneously var users = await Task.WhenAll(accounts.Select(async uid => (await _client.GetUserAsync(uid))?.NameAndMention() ?? $"(deleted account {uid})")); - var memberCount = await _members.MemberCount(system); + var memberCount = await _data.GetSystemMemberCount(system); var eb = new EmbedBuilder() .WithColor(Color.Blue) .WithTitle(system.Name ?? null) .WithThumbnailUrl(system.AvatarUrl ?? null) .WithFooter($"System ID: {system.Hid} | Created on {Formats.ZonedDateTimeFormat.Format(system.Created.InZone(system.Zone))}"); - var latestSwitch = await _switches.GetLatestSwitch(system); + var latestSwitch = await _data.GetLatestSwitch(system); if (latestSwitch != null) { - var switchMembers = (await _switches.GetSwitchMembers(latestSwitch)).ToList(); + var switchMembers = (await _data.GetSwitchMembers(latestSwitch)).ToList(); if (switchMembers.Count > 0) eb.AddField("Fronter".ToQuantity(switchMembers.Count(), ShowQuantityAs.None), string.Join(", ", switchMembers.Select(m => m.Name))); @@ -85,7 +80,7 @@ namespace PluralKit.Bot { color = Color.Default; } - var messageCount = await _members.MessageCount(member); + var messageCount = await _data.GetMemberMessageCount(member); var eb = new EmbedBuilder() // TODO: add URL of website when that's up @@ -108,7 +103,7 @@ namespace PluralKit.Bot { public async Task CreateFronterEmbed(PKSwitch sw, DateTimeZone zone) { - var members = (await _switches.GetSwitchMembers(sw)).ToList(); + var members = (await _data.GetSwitchMembers(sw)).ToList(); var timeSinceSwitch = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp; return new EmbedBuilder() .WithColor(members.FirstOrDefault()?.Color?.ToDiscordColor() ?? Color.Blue) @@ -125,7 +120,7 @@ namespace PluralKit.Bot { foreach (var sw in sws) { // Fetch member list and format - var members = (await _switches.GetSwitchMembers(sw)).ToList(); + var members = (await _data.GetSwitchMembers(sw)).ToList(); var membersStr = members.Any() ? string.Join(", ", members.Select(m => m.Name)) : "no fronter"; var switchSince = SystemClock.Instance.GetCurrentInstant() - sw.Timestamp; @@ -151,7 +146,7 @@ namespace PluralKit.Bot { .Build(); } - public async Task CreateMessageInfoEmbed(MessageStore.StoredMessage msg) + public async Task CreateMessageInfoEmbed(FullMessage msg) { var channel = await _client.GetChannelAsync(msg.Message.Channel) as ITextChannel; var serverMsg = channel != null ? await channel.GetMessageAsync(msg.Message.Mid) : null; @@ -193,20 +188,20 @@ namespace PluralKit.Bot { return eb.Build(); } - public Task CreateFrontPercentEmbed(SwitchStore.PerMemberSwitchDuration frontpercent, DateTimeZone tz) + public Task CreateFrontPercentEmbed(FrontBreakdown breakdown, DateTimeZone tz) { - var actualPeriod = frontpercent.RangeEnd - frontpercent.RangeStart; + var actualPeriod = breakdown.RangeEnd - breakdown.RangeStart; var eb = new EmbedBuilder() .WithColor(Color.Blue) - .WithFooter($"Since {Formats.ZonedDateTimeFormat.Format(frontpercent.RangeStart.InZone(tz))} ({Formats.DurationFormat.Format(actualPeriod)} ago)"); + .WithFooter($"Since {Formats.ZonedDateTimeFormat.Format(breakdown.RangeStart.InZone(tz))} ({Formats.DurationFormat.Format(actualPeriod)} ago)"); var maxEntriesToDisplay = 24; // max 25 fields allowed in embed - reserve 1 for "others" // We convert to a list of pairs so we can add the no-fronter value // Dictionary doesn't allow for null keys so we instead have a pair with a null key ;) - var pairs = frontpercent.MemberSwitchDurations.ToList(); - if (frontpercent.NoFronterDuration != Duration.Zero) - pairs.Add(new KeyValuePair(null, frontpercent.NoFronterDuration)); + var pairs = breakdown.MemberSwitchDurations.ToList(); + if (breakdown.NoFronterDuration != Duration.Zero) + pairs.Add(new KeyValuePair(null, breakdown.NoFronterDuration)); var membersOrdered = pairs.OrderByDescending(pair => pair.Value).Take(maxEntriesToDisplay).ToList(); foreach (var pair in membersOrdered) diff --git a/PluralKit.Bot/Services/PeriodicStatCollector.cs b/PluralKit.Bot/Services/PeriodicStatCollector.cs index 2baf2b78..7e4e42c8 100644 --- a/PluralKit.Bot/Services/PeriodicStatCollector.cs +++ b/PluralKit.Bot/Services/PeriodicStatCollector.cs @@ -17,10 +17,7 @@ namespace PluralKit.Bot private DiscordShardedClient _client; private IMetrics _metrics; - private SystemStore _systems; - private MemberStore _members; - private SwitchStore _switches; - private MessageStore _messages; + private IDataStore _data; private WebhookCacheService _webhookCache; @@ -28,16 +25,13 @@ namespace PluralKit.Bot private ILogger _logger; - public PeriodicStatCollector(IDiscordClient client, IMetrics metrics, SystemStore systems, MemberStore members, SwitchStore switches, MessageStore messages, ILogger logger, WebhookCacheService webhookCache, DbConnectionCountHolder countHolder) + public PeriodicStatCollector(IDiscordClient client, IMetrics metrics, ILogger logger, WebhookCacheService webhookCache, DbConnectionCountHolder countHolder, IDataStore data) { _client = (DiscordShardedClient) client; _metrics = metrics; - _systems = systems; - _members = members; - _switches = switches; - _messages = messages; _webhookCache = webhookCache; _countHolder = countHolder; + _data = data; _logger = logger.ForContext(); } @@ -65,10 +59,10 @@ namespace PluralKit.Bot _metrics.Measure.Gauge.SetValue(BotMetrics.MembersOnline, usersOnline.Count); // Aggregate DB stats - _metrics.Measure.Gauge.SetValue(CoreMetrics.SystemCount, await _systems.Count()); - _metrics.Measure.Gauge.SetValue(CoreMetrics.MemberCount, await _members.Count()); - _metrics.Measure.Gauge.SetValue(CoreMetrics.SwitchCount, await _switches.Count()); - _metrics.Measure.Gauge.SetValue(CoreMetrics.MessageCount, await _messages.Count()); + _metrics.Measure.Gauge.SetValue(CoreMetrics.SystemCount, await _data.GetTotalSystems()); + _metrics.Measure.Gauge.SetValue(CoreMetrics.MemberCount, await _data.GetTotalMembers()); + _metrics.Measure.Gauge.SetValue(CoreMetrics.SwitchCount, await _data.GetTotalSwitches()); + _metrics.Measure.Gauge.SetValue(CoreMetrics.MessageCount, await _data.GetTotalMessages()); // Process info var process = Process.GetCurrentProcess(); diff --git a/PluralKit.Bot/Services/ProxyService.cs b/PluralKit.Bot/Services/ProxyService.cs index 58b4996c..470e8a6b 100644 --- a/PluralKit.Bot/Services/ProxyService.cs +++ b/PluralKit.Bot/Services/ProxyService.cs @@ -23,7 +23,7 @@ namespace PluralKit.Bot class ProxyService: IDisposable { private IDiscordClient _client; private LogChannelService _logChannel; - private MessageStore _messageStorage; + private IDataStore _data; private EmbedService _embeds; private ILogger _logger; private WebhookExecutorService _webhookExecutor; @@ -31,11 +31,11 @@ namespace PluralKit.Bot private HttpClient _httpClient; - public ProxyService(IDiscordClient client, LogChannelService logChannel, MessageStore messageStorage, EmbedService embeds, ILogger logger, ProxyCacheService cache, WebhookExecutorService webhookExecutor) + public ProxyService(IDiscordClient client, LogChannelService logChannel, IDataStore data, EmbedService embeds, ILogger logger, ProxyCacheService cache, WebhookExecutorService webhookExecutor) { _client = client; _logChannel = logChannel; - _messageStorage = messageStorage; + _data = data; _embeds = embeds; _cache = cache; _webhookExecutor = webhookExecutor; @@ -115,7 +115,7 @@ namespace PluralKit.Bot ); // Store the message in the database, and log it in the log channel (if applicable) - await _messageStorage.Store(message.Author.Id, hookMessageId, message.Channel.Id, message.Id, match.Member); + await _data.AddMessage(message.Author.Id, hookMessageId, message.Channel.Id, message.Id, match.Member); await _logChannel.LogMessage(match.System, match.Member, hookMessageId, message.Id, message.Channel as IGuildChannel, message.Author, match.InnerText); // Wait a second or so before deleting the original message @@ -184,7 +184,7 @@ namespace PluralKit.Bot if (user == null) return; // Find the message in the DB - var msg = await _messageStorage.Get(message.Id); + var msg = await _data.GetMessage(message.Id); if (msg == null) return; // DM them the message card @@ -199,7 +199,7 @@ namespace PluralKit.Bot public async Task HandleMessageDeletionByReaction(Cacheable message, ulong userWhoReacted) { // Find the message in the database - var storedMessage = await _messageStorage.Get(message.Id); + var storedMessage = await _data.GetMessage(message.Id); if (storedMessage == null) return; // (if we can't, that's ok, no worries) // Make sure it's the actual sender of that message deleting the message @@ -215,7 +215,7 @@ namespace PluralKit.Bot } // Finally, delete it from our database. - await _messageStorage.Delete(message.Id); + await _data.DeleteMessage(message.Id); } public async Task HandleMessageDeletedAsync(Cacheable message, ISocketMessageChannel channel) @@ -224,13 +224,13 @@ namespace PluralKit.Bot // Non-webhook messages will never be stored anyway. // If we're not sure (eg. message outside of cache), delete just to be sure. if (message.HasValue && !message.Value.Author.IsWebhook) return; - await _messageStorage.Delete(message.Id); + await _data.DeleteMessage(message.Id); } public async Task HandleMessageBulkDeleteAsync(IReadOnlyCollection> messages, IMessageChannel channel) { _logger.Information("Bulk deleting {Count} messages in channel {Channel}", messages.Count, channel.Id); - await _messageStorage.BulkDelete(messages.Select(m => m.Id).ToList()); + await _data.DeleteMessagesBulk(messages.Select(m => m.Id).ToList()); } public void Dispose() diff --git a/PluralKit.Core/DataFiles.cs b/PluralKit.Core/DataFiles.cs index 8a9de1b3..403cbad7 100644 --- a/PluralKit.Core/DataFiles.cs +++ b/PluralKit.Core/DataFiles.cs @@ -12,16 +12,12 @@ namespace PluralKit.Bot { public class DataFileService { - private SystemStore _systems; - private MemberStore _members; - private SwitchStore _switches; + private IDataStore _data; private ILogger _logger; - public DataFileService(SystemStore systems, MemberStore members, SwitchStore switches, ILogger logger) + public DataFileService(ILogger logger, IDataStore data) { - _systems = systems; - _members = members; - _switches = switches; + _data = data; _logger = logger.ForContext(); } @@ -29,8 +25,8 @@ namespace PluralKit.Bot { // Export members var members = new List(); - var pkMembers = await _members.GetBySystem(system); // Read all members in the system - var messageCounts = await _members.MessageCountsPerMember(system); // Count messages proxied by all members in the system + var pkMembers = await _data.GetSystemMembers(system); // Read all members in the system + var messageCounts = await _data.GetMemberMessageCountBulk(system); // Count messages proxied by all members in the system members.AddRange(pkMembers.Select(m => new DataFileMember { Id = m.Hid, @@ -49,7 +45,7 @@ namespace PluralKit.Bot // Export switches var switches = new List(); - var switchList = await _switches.GetTruncatedSwitchList(system, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); + var switchList = await _data.GetPeriodFronters(system, Instant.FromDateTimeUtc(DateTime.MinValue.ToUniversalTime()), SystemClock.Instance.GetCurrentInstant()); switches.AddRange(switchList.Select(x => new DataFileSwitch { Timestamp = Formats.TimestampExportFormat.Format(x.TimespanStart), @@ -67,7 +63,7 @@ namespace PluralKit.Bot Members = members, Switches = switches, Created = Formats.TimestampExportFormat.Format(system.Created), - LinkedAccounts = (await _systems.GetLinkedAccountIds(system)).ToList() + LinkedAccounts = (await _data.GetSystemAccounts(system)).ToList() }; } @@ -85,7 +81,7 @@ namespace PluralKit.Bot // If we don't already have a system to save to, create one if (system == null) - system = await _systems.Create(data.Name); + system = await _data.CreateSystem(data.Name); result.System = system; // Apply system info @@ -94,13 +90,13 @@ namespace PluralKit.Bot if (data.Tag != null) system.Tag = data.Tag; if (data.AvatarUrl != null) system.AvatarUrl = data.AvatarUrl; if (data.TimeZone != null) system.UiTz = data.TimeZone ?? "UTC"; - await _systems.Save(system); + await _data.SaveSystem(system); // Make sure to link the sender account, too - await _systems.Link(system, accountId); + await _data.AddAccount(system, accountId); // Determine which members already exist and which ones need to be created - var existingMembers = await _members.GetBySystem(system); + var existingMembers = await _data.GetSystemMembers(system); foreach (var d in data.Members) { // Try to look up the member with the given ID @@ -134,7 +130,7 @@ namespace PluralKit.Bot // These consist of members from another PluralKit system or another framework (e.g. Tupperbox) var membersToCreate = new Dictionary(); unmappedMembers.ForEach(x => membersToCreate.Add(x.Id, x.Name)); - var newMembers = await _members.CreateMultiple(system, membersToCreate); + var newMembers = await _data.CreateMembersBulk(system, membersToCreate); foreach (var member in newMembers) dataFileToMemberMapping.Add(member.Key, member.Value); @@ -164,23 +160,26 @@ namespace PluralKit.Bot member.Birthday = birthdayParse.Success ? (LocalDate?)birthdayParse.Value : null; } - await _members.Save(member); + await _data.SaveMember(member); } // Re-map the switch members in the likely case IDs have changed - var mappedSwitches = new List>>(); + var mappedSwitches = new List(); foreach (var sw in data.Switches) { var timestamp = InstantPattern.ExtendedIso.Parse(sw.Timestamp).Value; var swMembers = new List(); swMembers.AddRange(sw.Members.Select(x => dataFileToMemberMapping.FirstOrDefault(y => y.Key.Equals(x)).Value)); - var mapped = new Tuple>(timestamp, swMembers); - mappedSwitches.Add(mapped); + mappedSwitches.Add(new ImportedSwitch + { + Timestamp = timestamp, + Members = swMembers + }); } // Import switches if (mappedSwitches.Any()) - await _switches.BulkImportSwitches(system, mappedSwitches); + await _data.AddSwitchesBulk(system, mappedSwitches); _logger.Information("Imported system {System}", system.Hid); return result; diff --git a/PluralKit.Core/Stores.cs b/PluralKit.Core/Stores.cs index aaef353b..78fc1fd8 100644 --- a/PluralKit.Core/Stores.cs +++ b/PluralKit.Core/Stores.cs @@ -11,22 +11,324 @@ using PluralKit.Core; using Serilog; namespace PluralKit { - public class SystemStore { + public class FullMessage + { + public PKMessage Message; + public PKMember Member; + public PKSystem System; + } + + public struct PKMessage + { + public ulong Mid; + public ulong Channel; + public ulong Sender; + public ulong? OriginalMid; + } + + public struct ImportedSwitch + { + public Instant Timestamp; + public IReadOnlyCollection Members; + } + + public struct SwitchListEntry + { + public ICollection Members; + public Instant TimespanStart; + public Instant TimespanEnd; + } + + public struct MemberMessageCount + { + public PKMember Member; + public int MessageCount; + } + + public struct FrontBreakdown + { + public Dictionary MemberSwitchDurations; + public Duration NoFronterDuration; + public Instant RangeStart; + public Instant RangeEnd; + } + + public interface IDataStore + { + /// + /// Gets a system by its internal system ID. + /// + /// The with the given internal ID, or null if no system was found. + Task GetSystemById(int systemId); + + /// + /// Gets a system by its user-facing human ID. + /// + /// The with the given human ID, or null if no system was found. + Task GetSystemByHid(string systemHid); + + /// + /// Gets a system by one of its linked Discord account IDs. Multiple IDs can return the same system. + /// + /// The with the given linked account, or null if no system was found. + Task GetSystemByAccount(ulong linkedAccount); + + /// + /// Gets a system by its API token. + /// + /// The with the given API token, or null if no corresponding system was found. + Task GetSystemByToken(string apiToken); + + /// + /// Gets the Discord account IDs linked to a system. + /// + /// An enumerable of Discord account IDs linked to this system. + Task> GetSystemAccounts(PKSystem system); + + /// + /// Gets the member count of a system. + /// + Task GetSystemMemberCount(PKSystem system); + + /// + /// Creates a system, auto-generating its corresponding IDs. + /// + /// An optional system name to set. If `null`, will not set a system name. + /// The created system model. + Task CreateSystem(string systemName); + // TODO: throw exception if account is present (when adding) or account isn't present (when removing) + + /// + /// Links a Discord account to a system. + /// + /// Throws an exception (TODO: which?) if the given account is already linked to a system. + Task AddAccount(PKSystem system, ulong accountToAdd); + + /// + /// Unlinks a Discord account from a system. + /// + /// Will *not* throw if this results in an orphaned system - this is the caller's responsibility to ensure. + /// + /// Throws an exception (TODO: which?) if the given account is not linked to the given system. + Task RemoveAccount(PKSystem system, ulong accountToRemove); + + /// + /// Saves the information within the given struct to the data store. + /// + Task SaveSystem(PKSystem system); + + /// + /// Deletes the given system from the database. + /// + /// + /// This will also delete all the system's members, all system switches, and every message that has been proxied + /// by members in the system. + /// + Task DeleteSystem(PKSystem system); + + /// + /// Gets a system by its internal member ID. + /// + /// The with the given internal ID, or null if no member was found. + Task GetMemberById(int memberId); + + /// + /// Gets a member by its user-facing human ID. + /// + /// The with the given human ID, or null if no member was found. + Task GetMemberByHid(string memberHid); + + /// + /// Gets a member by its member name within one system. + /// + /// + /// Member names are *usually* unique within a system (but not always), whereas member names + /// are almost certainly *not* unique globally - therefore only intra-system lookup is + /// allowed. + /// + /// The with the given name, or null if no member was found. + Task GetMemberByName(PKSystem system, string name); + + /// + /// Gets all members inside a given system. + /// + /// An enumerable of structs representing each member in the system, in no particular order. + Task> GetSystemMembers(PKSystem system); + /// + /// Gets the amount of messages proxied by a given member. + /// + /// The message count of the given member. + Task GetMemberMessageCount(PKMember member); + + /// + /// Collects a breakdown of each member in a system's message count. + /// + /// An enumerable of members along with their message counts. + Task> GetMemberMessageCountBulk(PKSystem system); + + /// + /// Creates a member, auto-generating its corresponding IDs. + /// + /// The system in which to create the member. + /// The name of the member to create. + /// The created system model. + Task CreateMember(PKSystem system, string name); + + /// + /// Creates multiple members, auto-generating each corresponding ID. + /// + /// The system to create the member in. + /// A dictionary containing a mapping from an arbitrary key to the member's name. + /// A dictionary containing the resulting member structs, each mapped to the key given in the argument dictionary. + Task> CreateMembersBulk(PKSystem system, Dictionary memberNames); + + /// + /// Saves the information within the given struct to the data store. + /// + Task SaveMember(PKMember member); + + /// + /// Deletes the given member from the database. + /// + /// + /// This will remove this member from any switches it's involved in, as well as all the messages + /// proxied by this member. + /// + Task DeleteMember(PKMember member); + + /// + /// Gets a message and its information by its ID. + /// + /// The message ID to look up. This can be either the ID of the trigger message containing the proxy tags or the resulting proxied webhook message. + /// An extended message object, containing not only the message data itself but the associated system and member structs. + Task GetMessage(ulong id); // id is both original and trigger, also add return type struct + + /// + /// Saves a posted message to the database. + /// + /// The ID of the account that sent the original trigger message. + /// The ID of the channel the message was posted to. + /// The ID of the message posted by the webhook. + /// The ID of the original trigger message containing the proxy tags. + /// The member (and by extension system) that was proxied. + /// + Task AddMessage(ulong senderAccount, ulong channelId, ulong postedMessageId, ulong triggerMessageId, PKMember proxiedMember); + + /// + /// Deletes a message from the data store. + /// + /// The ID of the webhook message to delete. + Task DeleteMessage(ulong postedMessageId); + + /// + /// Deletes messages from the data store in bulk. + /// + /// The IDs of the webhook messages to delete. + Task DeleteMessagesBulk(IEnumerable postedMessageIds); + + /// + /// Gets switches from a system. + /// + /// An enumerable of the *count* latest switches in the system, in latest-first order. May contain fewer elements than requested. + Task> GetSwitches(PKSystem system, int count); + + /// + /// Gets the latest (temporally; closest to now) switch of a given system. + /// + Task GetLatestSwitch(PKSystem system); + + /// + /// Gets the members a given switch consists of. + /// + Task> GetSwitchMembers(PKSwitch sw); + + /// + /// Gets a list of fronters over a given period of time. + /// + /// + /// This list is returned as an enumerable of "switch members", each containing a timestamp + /// and a member ID. + /// + /// Switches containing multiple members will be returned as multiple switch members each with the same + /// timestamp, and a change in timestamp should be interpreted as the start of a new switch. + /// + /// An enumerable of the aforementioned "switch members". + Task> GetPeriodFronters(PKSystem system, Instant periodStart, Instant periodEnd); + + /// + /// Calculates a breakdown of a system's fronters over a given period, including how long each member has + /// been fronting, and how long *no* member has been fronting. + /// + /// + /// Switches containing multiple members will count the full switch duration for all members, meaning + /// the total duration may add up to longer than the breakdown period. + /// + /// + /// + /// + /// + Task GetFrontBreakdown(PKSystem system, Instant periodStart, Instant periodEnd); + + /// + /// Registers a switch with the given members in the given system. + /// + /// Throws an exception (TODO: which?) if any of the members are not in the given system. + Task AddSwitch(PKSystem system, IEnumerable switchMembers); + + /// + /// Registers switches in bulk. + /// + /// A list of switch structs, each containing a timestamp and a list of members. + /// Throws an exception (TODO: which?) if any of the given members are not in the given system. + Task AddSwitchesBulk(PKSystem system, IEnumerable switches); + + /// + /// Updates the timestamp of a given switch. + /// + Task MoveSwitch(PKSwitch sw, Instant time); + + /// + /// Deletes a given switch from the data store. + /// + Task DeleteSwitch(PKSwitch sw); + + /// + /// Gets the total amount of systems in the data store. + /// + Task GetTotalSystems(); + + /// + /// Gets the total amount of members in the data store. + /// + Task GetTotalMembers(); + + /// + /// Gets the total amount of switches in the data store. + /// + Task GetTotalSwitches(); + + /// + /// Gets the total amount of messages in the data store. + /// + Task GetTotalMessages(); + } + + public class PostgresDataStore: IDataStore { private DbConnectionFactory _conn; private ILogger _logger; - public SystemStore(DbConnectionFactory conn, ILogger logger) + public PostgresDataStore(DbConnectionFactory conn, ILogger logger) { - this._conn = conn; - _logger = logger.ForContext(); + _conn = conn; + _logger = logger; } - public async Task Create(string systemName = null) { + public async Task CreateSystem(string systemName = null) { string hid; do { hid = Utils.GenerateHid(); - } while (await GetByHid(hid) != null); + } while (await GetSystemByHid(hid) != null); PKSystem system; using (var conn = await _conn.Obtain()) @@ -36,7 +338,7 @@ namespace PluralKit { return system; } - public async Task Link(PKSystem system, ulong accountId) { + public async Task AddAccount(PKSystem system, ulong accountId) { // We have "on conflict do nothing" since linking an account when it's already linked to the same system is idempotent // This is used in import/export, although the pk;link command checks for this case beforehand using (var conn = await _conn.Obtain()) @@ -45,76 +347,65 @@ namespace PluralKit { _logger.Information("Linked system {System} to account {Account}", system.Id, accountId); } - public async Task Unlink(PKSystem system, ulong accountId) { + public async Task RemoveAccount(PKSystem system, ulong accountId) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("delete from accounts where uid = @Id and system = @SystemId", new { Id = accountId, SystemId = system.Id }); _logger.Information("Unlinked system {System} from account {Account}", system.Id, accountId); } - public async Task GetByAccount(ulong accountId) { + public async Task GetSystemByAccount(ulong accountId) { using (var conn = await _conn.Obtain()) return await conn.QuerySingleOrDefaultAsync("select systems.* from systems, accounts where accounts.system = systems.id and accounts.uid = @Id", new { Id = accountId }); } - public async Task GetByHid(string hid) { + public async Task GetSystemByHid(string hid) { using (var conn = await _conn.Obtain()) return await conn.QuerySingleOrDefaultAsync("select * from systems where systems.hid = @Hid", new { Hid = hid.ToLower() }); } - public async Task GetByToken(string token) { + public async Task GetSystemByToken(string token) { using (var conn = await _conn.Obtain()) return await conn.QuerySingleOrDefaultAsync("select * from systems where token = @Token", new { Token = token }); } - public async Task GetById(int id) + public async Task GetSystemById(int id) { using (var conn = await _conn.Obtain()) return await conn.QuerySingleOrDefaultAsync("select * from systems where id = @Id", new { Id = id }); } - public async Task Save(PKSystem system) { + public async Task SaveSystem(PKSystem system) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("update systems set name = @Name, description = @Description, tag = @Tag, avatar_url = @AvatarUrl, token = @Token, ui_tz = @UiTz where id = @Id", system); _logger.Information("Updated system {@System}", system); } - public async Task Delete(PKSystem system) { + public async Task DeleteSystem(PKSystem system) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("delete from systems where id = @Id", system); _logger.Information("Deleted system {System}", system.Id); } - public async Task> GetLinkedAccountIds(PKSystem system) + public async Task> GetSystemAccounts(PKSystem system) { using (var conn = await _conn.Obtain()) return await conn.QueryAsync("select uid from accounts where system = @Id", new { Id = system.Id }); } - public async Task Count() + public async Task GetTotalSystems() { using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync("select count(id) from systems"); } - } - public class MemberStore { - private DbConnectionFactory _conn; - private ILogger _logger; - - public MemberStore(DbConnectionFactory conn, ILogger logger) - { - this._conn = conn; - _logger = logger.ForContext(); - } - - public async Task Create(PKSystem system, string name) { + public async Task CreateMember(PKSystem system, string name) { string hid; do { hid = Utils.GenerateHid(); - } while (await GetByHid(hid) != null); + } while (await GetMemberByHid(hid) != null); PKMember member; using (var conn = await _conn.Obtain()) @@ -128,7 +419,7 @@ namespace PluralKit { return member; } - public async Task> CreateMultiple(PKSystem system, Dictionary names) + public async Task> CreateMembersBulk(PKSystem system, Dictionary names) { using (var conn = await _conn.Obtain()) using (var tx = conn.BeginTransaction()) @@ -159,60 +450,59 @@ namespace PluralKit { } } - public async Task GetByHid(string hid) { + public async Task GetMemberById(int id) { + using (var conn = await _conn.Obtain()) + return await conn.QuerySingleOrDefaultAsync("select * from members where id = @Id", new { Id = id }); + } + + public async Task GetMemberByHid(string hid) { using (var conn = await _conn.Obtain()) return await conn.QuerySingleOrDefaultAsync("select * from members where hid = @Hid", new { Hid = hid.ToLower() }); } - public async Task GetByName(PKSystem system, string name) { + public async Task GetMemberByName(PKSystem system, string name) { // QueryFirst, since members can (in rare cases) share names using (var conn = await _conn.Obtain()) return await conn.QueryFirstOrDefaultAsync("select * from members where lower(name) = lower(@Name) and system = @SystemID", new { Name = name, SystemID = system.Id }); } public async Task> GetUnproxyableMembers(PKSystem system) { - return (await GetBySystem(system)) + return (await GetSystemMembers(system)) .Where((m) => { var proxiedName = $"{m.Name} {system.Tag}"; return proxiedName.Length > Limits.MaxProxyNameLength || proxiedName.Length < 2; }).ToList(); } - public async Task> GetBySystem(PKSystem system) { + public async Task> GetSystemMembers(PKSystem system) { using (var conn = await _conn.Obtain()) return await conn.QueryAsync("select * from members where system = @SystemID", new { SystemID = system.Id }); } - public async Task Save(PKMember member) { + public async Task SaveMember(PKMember member) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("update members set name = @Name, display_name = @DisplayName, description = @Description, color = @Color, avatar_url = @AvatarUrl, birthday = @Birthday, pronouns = @Pronouns, prefix = @Prefix, suffix = @Suffix where id = @Id", member); _logger.Information("Updated member {@Member}", member); } - public async Task Delete(PKMember member) { + public async Task DeleteMember(PKMember member) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("delete from members where id = @Id", member); _logger.Information("Deleted member {@Member}", member); } - public async Task MessageCount(PKMember member) + public async Task GetMemberMessageCount(PKMember member) { using (var conn = await _conn.Obtain()) - return await conn.QuerySingleAsync("select count(*) from messages where member = @Id", member); + return await conn.QuerySingleAsync("select count(*) from messages where member = @Id", member); } - public struct MessageBreakdownListEntry - { - public int Member; - public int MessageCount; - } - - public async Task> MessageCountsPerMember(PKSystem system) + public async Task> GetMemberMessageCountBulk(PKSystem system) { using (var conn = await _conn.Obtain()) - return await conn.QueryAsync( + return await conn.QueryAsync( @"SELECT messages.member, COUNT(messages.member) messagecount FROM members JOIN messages @@ -222,44 +512,18 @@ namespace PluralKit { new { System = system.Id }); } - public async Task MemberCount(PKSystem system) + public async Task GetSystemMemberCount(PKSystem system) { using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync("select count(*) from members where system = @Id", system); } - public async Task Count() + public async Task GetTotalMembers() { using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync("select count(id) from members"); } - } - - public class MessageStore { - public struct PKMessage - { - public ulong Mid; - public ulong Channel; - public ulong Sender; - public ulong? OriginalMid; - } - public class StoredMessage - { - public PKMessage Message; - public PKMember Member; - public PKSystem System; - } - - private DbConnectionFactory _conn; - private ILogger _logger; - - public MessageStore(DbConnectionFactory conn, ILogger logger) - { - this._conn = conn; - _logger = logger.ForContext(); - } - - public async Task Store(ulong senderId, ulong messageId, ulong channelId, ulong originalMessage, PKMember member) { + public async Task AddMessage(ulong senderId, ulong messageId, ulong channelId, ulong originalMessage, PKMember member) { using (var conn = await _conn.Obtain()) await conn.ExecuteAsync("insert into messages(mid, channel, member, sender, original_mid) values(@MessageId, @ChannelId, @MemberId, @SenderId, @OriginalMid)", new { MessageId = messageId, @@ -272,10 +536,10 @@ namespace PluralKit { _logger.Information("Stored message {Message} in channel {Channel}", messageId, channelId); } - public async Task Get(ulong id) + public async Task GetMessage(ulong id) { using (var conn = await _conn.Obtain()) - return (await conn.QueryAsync("select messages.*, members.*, systems.* from messages, members, systems where (mid = @Id or original_mid = @Id) and messages.member = members.id and systems.id = members.system", (msg, member, system) => new StoredMessage + return (await conn.QueryAsync("select messages.*, members.*, systems.* from messages, members, systems where (mid = @Id or original_mid = @Id) and messages.member = members.id and systems.id = members.system", (msg, member, system) => new FullMessage { Message = msg, System = system, @@ -283,13 +547,13 @@ namespace PluralKit { }, new { Id = id })).FirstOrDefault(); } - public async Task Delete(ulong id) { + public async Task DeleteMessage(ulong id) { using (var conn = await _conn.Obtain()) if (await conn.ExecuteAsync("delete from messages where mid = @Id", new { Id = id }) > 0) _logger.Information("Deleted message {Message}", id); } - public async Task BulkDelete(IReadOnlyCollection ids) + public async Task DeleteMessagesBulk(IEnumerable ids) { using (var conn = await _conn.Obtain()) { @@ -301,25 +565,13 @@ namespace PluralKit { } } - public async Task Count() + public async Task GetTotalMessages() { using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync("select count(mid) from messages"); } - } - public class SwitchStore - { - private DbConnectionFactory _conn; - private ILogger _logger; - - public SwitchStore(DbConnectionFactory conn, ILogger logger) - { - _conn = conn; - _logger = logger.ForContext(); - } - - public async Task RegisterSwitch(PKSystem system, ICollection members) + public async Task AddSwitch(PKSystem system, IEnumerable members) { // Use a transaction here since we're doing multiple executed commands in one using (var conn = await _conn.Obtain()) @@ -345,7 +597,7 @@ namespace PluralKit { } } - public async Task BulkImportSwitches(PKSystem system, ICollection>> switches) + public async Task AddSwitchesBulk(PKSystem system, IEnumerable switches) { // Read existing switches to enforce unique timestamps var priorSwitches = await GetSwitches(system); @@ -363,13 +615,13 @@ namespace PluralKit { foreach (var sw in switches) { // If there's already a switch at this time, move on - if (priorSwitches.Any(x => x.Timestamp.Equals(sw.Item1))) + if (priorSwitches.Any(x => x.Timestamp.Equals(sw.Timestamp))) continue; // Otherwise, add it to the importer importer.StartRow(); importer.Write(system.Id, NpgsqlTypes.NpgsqlDbType.Integer); - importer.Write(sw.Item1, NpgsqlTypes.NpgsqlDbType.Timestamp); + importer.Write(sw.Members, NpgsqlTypes.NpgsqlDbType.Timestamp); } importer.Complete(); // Commits the copy operation so dispose won't roll it back } @@ -392,12 +644,12 @@ namespace PluralKit { foreach (var pkSwitch in switchesWithoutMembers) { // If this isn't in our import set, move on - var sw = switches.FirstOrDefault(x => x.Item1.Equals(pkSwitch.Timestamp)); + var sw = switches.Select(x => (ImportedSwitch?) x).FirstOrDefault(x => x.Value.Timestamp.Equals(pkSwitch.Timestamp)); if (sw == null) continue; // Loop through associated members to add each to the switch - foreach (var m in sw.Item2) + foreach (var m in sw.Value.Members) { // Skip switch-outs - these don't have switch_members if (m == null) @@ -534,20 +786,13 @@ namespace PluralKit { _logger.Information("Deleted switch {Switch}"); } - public async Task Count() + public async Task GetTotalSwitches() { using (var conn = await _conn.Obtain()) return await conn.ExecuteScalarAsync("select count(id) from switches"); } - public struct SwitchListEntry - { - public ICollection Members; - public Instant TimespanStart; - public Instant TimespanEnd; - } - - public async Task> GetTruncatedSwitchList(PKSystem system, Instant periodStart, Instant periodEnd) + public async Task> GetPeriodFronters(PKSystem system, Instant periodStart, Instant periodEnd) { // Returns the timestamps and member IDs of switches overlapping the range, in chronological (newest first) order var switchMembers = await GetSwitchMembersList(system, periodStart, periodEnd); @@ -599,17 +844,7 @@ namespace PluralKit { return outList; } - - public struct PerMemberSwitchDuration - { - public Dictionary MemberSwitchDurations; - public Duration NoFronterDuration; - public Instant RangeStart; - public Instant RangeEnd; - } - - public async Task GetPerMemberSwitchDuration(PKSystem system, Instant periodStart, - Instant periodEnd) + public async Task GetFrontBreakdown(PKSystem system, Instant periodStart, Instant periodEnd) { var dict = new Dictionary(); @@ -621,7 +856,7 @@ namespace PluralKit { var actualStart = periodEnd; // will be "pulled" down var actualEnd = periodStart; // will be "pulled" up - foreach (var sw in await GetTruncatedSwitchList(system, periodStart, periodEnd)) + foreach (var sw in await GetPeriodFronters(system, periodStart, periodEnd)) { var span = sw.TimespanEnd - sw.TimespanStart; foreach (var member in sw.Members) @@ -636,7 +871,7 @@ namespace PluralKit { if (sw.TimespanEnd > actualEnd) actualEnd = sw.TimespanEnd; } - return new PerMemberSwitchDuration + return new FrontBreakdown { MemberSwitchDurations = dict, NoFronterDuration = noFronterDuration, diff --git a/PluralKit.Web/Pages/ViewSystem.cshtml.cs b/PluralKit.Web/Pages/ViewSystem.cshtml.cs index b231e7f4..ebd41064 100644 --- a/PluralKit.Web/Pages/ViewSystem.cshtml.cs +++ b/PluralKit.Web/Pages/ViewSystem.cshtml.cs @@ -7,13 +7,11 @@ namespace PluralKit.Web.Pages { public class ViewSystem : PageModel { - private SystemStore _systems; - private MemberStore _members; + private IDataStore _data; - public ViewSystem(SystemStore systems, MemberStore members) + public ViewSystem(IDataStore data) { - _systems = systems; - _members = members; + _data = data; } public PKSystem System { get; set; } @@ -21,10 +19,10 @@ namespace PluralKit.Web.Pages public async Task OnGet(string systemId) { - System = await _systems.GetByHid(systemId); + System = await _data.GetSystemByHid(systemId); if (System == null) return NotFound(); - Members = await _members.GetBySystem(System); + Members = await _data.GetSystemMembers(System); return Page(); } diff --git a/PluralKit.Web/Startup.cs b/PluralKit.Web/Startup.cs index 050e1635..f7b56233 100644 --- a/PluralKit.Web/Startup.cs +++ b/PluralKit.Web/Startup.cs @@ -28,8 +28,7 @@ namespace PluralKit.Web services .AddScoped(_ => new NpgsqlConnection(config.Database)) - .AddTransient() - .AddTransient() + .AddTransient() .AddSingleton(config); }