From 7b9d2a4e5eac708c74c4d8124dfe81d9c63a6edc Mon Sep 17 00:00:00 2001 From: spiral Date: Fri, 19 Nov 2021 11:37:50 -0500 Subject: [PATCH] feat(webhooks): add all events except group member events --- .../Repository/ModelRepository.Account.cs | 1 + .../Repository/ModelRepository.Group.cs | 26 ++++++++-- .../Repository/ModelRepository.Guild.cs | 8 +-- .../Repository/ModelRepository.Member.cs | 32 +++++++++++- .../Repository/ModelRepository.Switch.cs | 51 ++++++++++++++++--- .../Repository/ModelRepository.System.cs | 29 +++++++++-- PluralKit.Core/Dispatch/DispatchService.cs | 17 +++++++ PluralKit.Core/Models/Patch/AccountPatch.cs | 12 +++++ 8 files changed, 156 insertions(+), 20 deletions(-) diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Account.cs b/PluralKit.Core/Database/Repository/ModelRepository.Account.cs index bba07815..2839df38 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Account.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Account.cs @@ -10,6 +10,7 @@ namespace PluralKit.Core { _logger.Information("Updated account {accountId}: {@AccountPatch}", id, patch); var query = patch.Apply(new Query("accounts").Where("uid", id)); + _ = _dispatch.Dispatch(id, patch); await _db.ExecuteQuery(query, extraSql: "returning *"); } } diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Group.cs b/PluralKit.Core/Database/Repository/ModelRepository.Group.cs index ba7fe956..4a51968c 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Group.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Group.cs @@ -2,6 +2,8 @@ using System; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + using SqlKata; namespace PluralKit.Core @@ -62,22 +64,38 @@ namespace PluralKit.Core name = name }); var group = await _db.QueryFirst(conn, query, extraSql: "returning *"); + _ = _dispatch.Dispatch(group.Id, new UpdateDispatchData() + { + Event = DispatchEvent.CREATE_GROUP, + EventData = JObject.FromObject(new { name = name }), + }); _logger.Information("Created group {GroupId} in system {SystemId}: {GroupName}", group.Id, system, name); return group; } - public Task UpdateGroup(GroupId id, GroupPatch patch, IPKConnection? conn = null) + public async Task UpdateGroup(GroupId id, GroupPatch patch, IPKConnection? conn = null) { _logger.Information("Updated {GroupId}: {@GroupPatch}", id, patch); var query = patch.Apply(new Query("groups").Where("id", id)); - return _db.QueryFirst(conn, query, extraSql: "returning *"); + var group = await _db.QueryFirst(conn, query, extraSql: "returning *"); + _ = _dispatch.Dispatch(id, new() + { + Event = DispatchEvent.UPDATE_GROUP, + EventData = patch.ToJson(), + }); + return group; } - public Task DeleteGroup(GroupId group) + public async Task DeleteGroup(GroupId group) { + var oldGroup = await GetGroup(group); + _logger.Information("Deleted {GroupId}", group); var query = new Query("groups").AsDelete().Where("id", group); - return _db.ExecuteQuery(query); + await _db.ExecuteQuery(query); + + if (oldGroup != null) + _ = _dispatch.Dispatch(oldGroup.System, oldGroup.Uuid, DispatchEvent.DELETE_GROUP); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs index 851c83d2..df36dffc 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs @@ -39,14 +39,15 @@ namespace PluralKit.Core ); } - public Task UpdateSystemGuild(SystemId system, ulong guild, SystemGuildPatch patch) + public async Task UpdateSystemGuild(SystemId system, ulong guild, SystemGuildPatch patch) { _logger.Information("Updated {SystemId} in guild {GuildId}: {@SystemGuildPatch}", system, guild, patch); var query = patch.Apply(new Query("system_guild").Where("system", system).Where("guild", guild)); - return _db.QueryFirst(query, extraSql: "returning *"); + var settings = await _db.QueryFirst(query, extraSql: "returning *"); + _ = _dispatch.Dispatch(system, guild, patch); + return settings; } - public Task GetMemberGuild(ulong guild, MemberId member, bool defaultInsert = true) { if (!defaultInsert) @@ -69,6 +70,7 @@ namespace PluralKit.Core { _logger.Information("Updated {MemberId} in guild {GuildId}: {@MemberGuildPatch}", member, guild, patch); var query = patch.Apply(new Query("member_guild").Where("member", member).Where("guild", guild)); + _ = _dispatch.Dispatch(member, guild, patch); return _db.QueryFirst(query, extraSql: "returning *"); } } diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Member.cs b/PluralKit.Core/Database/Repository/ModelRepository.Member.cs index d2cf4e4f..dc32b7f8 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Member.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Member.cs @@ -1,7 +1,10 @@ #nullable enable using System; +using System.Collections.Generic; using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + using SqlKata; namespace PluralKit.Core @@ -46,6 +49,15 @@ namespace PluralKit.Core return _db.QueryFirst(query); } + public Task> GetMemberGuids(IEnumerable ids) + { + var query = new Query("members") + .Select("uuid") + .WhereIn("id", ids); + + return _db.Query(query); + } + public async Task CreateMember(SystemId systemId, string memberName, IPKConnection? conn = null) { var query = new Query("members").AsInsert(new @@ -57,6 +69,11 @@ namespace PluralKit.Core var member = await _db.QueryFirst(conn, query, "returning *"); _logger.Information("Created {MemberId} in {SystemId}: {MemberName}", member.Id, systemId, memberName); + _ = _dispatch.Dispatch(member.Id, new() + { + Event = DispatchEvent.CREATE_MEMBER, + EventData = JObject.FromObject(new { name = memberName }), + }); return member; } @@ -64,14 +81,25 @@ namespace PluralKit.Core { _logger.Information("Updated {MemberId}: {@MemberPatch}", id, patch); var query = patch.Apply(new Query("members").Where("id", id)); + _ = _dispatch.Dispatch(id, new() + { + Event = DispatchEvent.UPDATE_MEMBER, + EventData = patch.ToJson(), + }); return _db.QueryFirst(conn, query, extraSql: "returning *"); } - public Task DeleteMember(MemberId id) + public async Task DeleteMember(MemberId id) { + var oldMember = await GetMember(id); + _logger.Information("Deleted {MemberId}", id); var query = new Query("members").AsDelete().Where("id", id); - return _db.ExecuteQuery(query); + await _db.ExecuteQuery(query); + + // shh, compiler + if (oldMember != null) + _ = _dispatch.Dispatch(oldMember.System, oldMember.Uuid, DispatchEvent.DELETE_MEMBER); } } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs index b34d5909..8282d8ad 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Switch.cs @@ -5,6 +5,8 @@ using System.Threading.Tasks; using Dapper; +using Newtonsoft.Json.Linq; + using NodaTime; using NpgsqlTypes; @@ -42,8 +44,19 @@ namespace PluralKit.Core await tx.CommitAsync(); _logger.Information("Created {SwitchId} in {SystemId}: {Members}", sw.Id, system, members); + _ = _dispatch.Dispatch(sw.Id, new() + { + Event = DispatchEvent.CREATE_SWITCH, + EventData = JObject.FromObject(new + { + id = sw.Uuid.ToString(), + timestamp = sw.Timestamp.FormatExport(), + members = await GetMemberGuids(members), + }), + }); return sw; } + public async Task EditSwitch(IPKConnection conn, SwitchId switchId, IReadOnlyCollection members) { // Use a transaction here since we're doing multiple executed commands in one @@ -69,28 +82,52 @@ namespace PluralKit.Core // Finally we commit the tx, since the using block will otherwise rollback it await tx.CommitAsync(); + _ = _dispatch.Dispatch(switchId, new() + { + Event = DispatchEvent.UPDATE_SWITCH_MEMBERS, + EventData = JObject.FromObject(new + { + members = await GetMemberGuids(members), + }), + }); + _logger.Information("Updated {SwitchId} members: {Members}", switchId, members); } - public Task MoveSwitch(SwitchId id, Instant time) + public async Task MoveSwitch(SwitchId id, Instant time) { _logger.Information("Updated {SwitchId} timestamp: {SwitchTimestamp}", id, time); var query = new Query("switches").AsUpdate(new { timestamp = time }).Where("id", id); - return _db.ExecuteQuery(query); + await _db.ExecuteQuery(query); + _ = _dispatch.Dispatch(id, new() + { + Event = DispatchEvent.UPDATE_SWITCH, + EventData = JObject.FromObject(new + { + timestamp = time.FormatExport(), + }), + }); } - public Task DeleteSwitch(SwitchId id) + public async Task DeleteSwitch(SwitchId id) { - _logger.Information("Deleted {Switch}", id); + var existingSwitch = await GetSwitch(id); + var query = new Query("switches").AsDelete().Where("id", id); - return _db.ExecuteQuery(query); + await _db.ExecuteQuery(query); + _logger.Information("Deleted {Switch}", id); + _ = _dispatch.Dispatch(existingSwitch.System, existingSwitch.Uuid, DispatchEvent.DELETE_SWITCH); } - public Task DeleteAllSwitches(SystemId system) + public async Task DeleteAllSwitches(SystemId system) { _logger.Information("Deleted all switches in {SystemId}", system); var query = new Query("switches").AsDelete().Where("system", system); - return _db.ExecuteQuery(query); + await _db.ExecuteQuery(query); + _ = _dispatch.Dispatch(system, new UpdateDispatchData() + { + Event = DispatchEvent.DELETE_ALL_SWITCHES + }); } public IAsyncEnumerable GetSwitches(SystemId system) diff --git a/PluralKit.Core/Database/Repository/ModelRepository.System.cs b/PluralKit.Core/Database/Repository/ModelRepository.System.cs index 805892c9..5ba19fc3 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.System.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.System.cs @@ -78,17 +78,27 @@ namespace PluralKit.Core }); var system = await _db.QueryFirst(conn, query, extraSql: "returning *"); _logger.Information("Created {SystemId}", system.Id); + + // no dispatch call here - system was just created, we don't have a webhook URL return system; } - public Task UpdateSystem(SystemId id, SystemPatch patch, IPKConnection? conn = null) + public async Task UpdateSystem(SystemId id, SystemPatch patch, IPKConnection? conn = null) { _logger.Information("Updated {SystemId}: {@SystemPatch}", id, patch); var query = patch.Apply(new Query("systems").Where("id", id)); - return _db.QueryFirst(conn, query, extraSql: "returning *"); + var res = await _db.QueryFirst(conn, query, extraSql: "returning *"); + + _ = _dispatch.Dispatch(id, new UpdateDispatchData() + { + Event = DispatchEvent.UPDATE_SYSTEM, + EventData = patch.ToJson(), + }); + + return res; } - public Task AddAccount(SystemId system, ulong accountId, IPKConnection? conn = null) + public async Task AddAccount(SystemId system, ulong accountId, IPKConnection? conn = null) { // 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 @@ -100,7 +110,13 @@ namespace PluralKit.Core }); _logger.Information("Linked account {UserId} to {SystemId}", accountId, system); - return _db.ExecuteQuery(conn, query, extraSql: "on conflict do nothing"); + await _db.ExecuteQuery(conn, query, extraSql: "on conflict do nothing"); + + _ = _dispatch.Dispatch(system, new UpdateDispatchData() + { + Event = DispatchEvent.LINK_ACCOUNT, + EntityId = accountId.ToString(), + }); } public async Task RemoveAccount(SystemId system, ulong accountId) @@ -108,6 +124,11 @@ namespace PluralKit.Core var query = new Query("accounts").AsDelete().Where("uid", accountId).Where("system", system); await _db.ExecuteQuery(query); _logger.Information("Unlinked account {UserId} from {SystemId}", accountId, system); + _ = _dispatch.Dispatch(system, new UpdateDispatchData() + { + Event = DispatchEvent.UNLINK_ACCOUNT, + EntityId = accountId.ToString(), + }); } public Task DeleteSystem(SystemId id) diff --git a/PluralKit.Core/Dispatch/DispatchService.cs b/PluralKit.Core/Dispatch/DispatchService.cs index 613d99b9..367f2052 100644 --- a/PluralKit.Core/Dispatch/DispatchService.cs +++ b/PluralKit.Core/Dispatch/DispatchService.cs @@ -191,6 +191,23 @@ namespace PluralKit.Core await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody()); } + public async Task Dispatch(ulong accountId, AccountPatch patch) + { + var repo = _provider.Resolve(); + var system = await repo.GetSystemByAccount(accountId); + if (system.WebhookUrl == null) + return; + + var data = new UpdateDispatchData(); + data.Event = DispatchEvent.UPDATE_MEMBER_GUILD; + data.SigningToken = system.WebhookToken; + data.EntityId = accountId.ToString(); + data.EventData = patch.ToJson(); + + _logger.Debug("Dispatching webhook for account {AccountId} (system {SystemId})", accountId, system.Id); + await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody()); + } + public async Task Dispatch(SystemId systemId, Guid uuid, DispatchEvent evt) { var repo = _provider.Resolve(); diff --git a/PluralKit.Core/Models/Patch/AccountPatch.cs b/PluralKit.Core/Models/Patch/AccountPatch.cs index 9f2480d0..a1dca69d 100644 --- a/PluralKit.Core/Models/Patch/AccountPatch.cs +++ b/PluralKit.Core/Models/Patch/AccountPatch.cs @@ -1,5 +1,7 @@ using SqlKata; +using Newtonsoft.Json.Linq; + namespace PluralKit.Core { public class AccountPatch: PatchObject @@ -9,5 +11,15 @@ namespace PluralKit.Core public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper .With("allow_autoproxy", AllowAutoproxy) ); + + public JObject ToJson() + { + var o = new JObject(); + + if (AllowAutoproxy.IsPresent) + o.Add("allow_autoproxy", AllowAutoproxy.Value); + + return o; + } } } \ No newline at end of file