Merge branch 'feat/webhooks' into main

This commit is contained in:
spiral
2021-11-25 17:15:42 -05:00
34 changed files with 920 additions and 39 deletions

View File

@@ -0,0 +1,7 @@
-- schema version 20: insert date
-- add outgoing webhook to systems
alter table systems add column webhook_url text;
alter table systems add column webhook_token text;
update info set schema_version = 20;

View File

@@ -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 *");
}
}

View File

@@ -2,12 +2,20 @@
using System;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using SqlKata;
namespace PluralKit.Core
{
public partial class ModelRepository
{
public Task<PKGroup?> GetGroup(GroupId id)
{
var query = new Query("groups").Where("id", id);
return _db.QueryFirst<PKGroup?>(query);
}
public Task<PKGroup?> GetGroupByName(SystemId system, string name)
{
var query = new Query("groups").Where("system", system).WhereRaw("lower(name) = lower(?)", name.ToLower());
@@ -60,18 +68,31 @@ namespace PluralKit.Core
return group;
}
public Task<PKGroup> UpdateGroup(GroupId id, GroupPatch patch, IPKConnection? conn = null)
public async Task<PKGroup> 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<PKGroup>(conn, query, extraSql: "returning *");
var group = await _db.QueryFirst<PKGroup>(conn, query, extraSql: "returning *");
if (conn == null)
_ = _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);
}
}
}

View File

@@ -39,14 +39,15 @@ namespace PluralKit.Core
);
}
public Task<SystemGuildSettings> UpdateSystemGuild(SystemId system, ulong guild, SystemGuildPatch patch)
public async Task<SystemGuildSettings> 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<SystemGuildSettings>(query, extraSql: "returning *");
var settings = await _db.QueryFirst<SystemGuildSettings>(query, extraSql: "returning *");
_ = _dispatch.Dispatch(system, guild, patch);
return settings;
}
public Task<MemberGuildSettings> 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<MemberGuildSettings>(query, extraSql: "returning *");
}
}

View File

@@ -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<PKMember?>(query);
}
public Task<IEnumerable<Guid>> GetMemberGuids(IEnumerable<MemberId> ids)
{
var query = new Query("members")
.Select("uuid")
.WhereIn("id", ids);
return _db.Query<Guid>(query);
}
public async Task<PKMember> CreateMember(SystemId systemId, string memberName, IPKConnection? conn = null)
{
var query = new Query("members").AsInsert(new
@@ -64,14 +76,27 @@ namespace PluralKit.Core
{
_logger.Information("Updated {MemberId}: {@MemberPatch}", id, patch);
var query = patch.Apply(new Query("members").Where("id", id));
if (conn == null)
_ = _dispatch.Dispatch(id, new()
{
Event = DispatchEvent.UPDATE_MEMBER,
EventData = patch.ToJson(),
});
return _db.QueryFirst<PKMember>(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);
}
}
}

View File

@@ -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<MemberId> 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<PKSwitch> GetSwitches(SystemId system)
@@ -100,6 +137,9 @@ namespace PluralKit.Core
return _db.QueryStream<PKSwitch>(query);
}
public Task<PKSwitch?> GetSwitch(SwitchId id)
=> _db.QueryFirst<PKSwitch?>(new Query("switches").Where("id", id));
public Task<PKSwitch> GetSwitchByUuid(Guid uuid)
{
var query = new Query("switches").Where("uuid", uuid);

View File

@@ -78,17 +78,27 @@ namespace PluralKit.Core
});
var system = await _db.QueryFirst<PKSystem>(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<PKSystem> UpdateSystem(SystemId id, SystemPatch patch, IPKConnection? conn = null)
public async Task<PKSystem> 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<PKSystem>(conn, query, extraSql: "returning *");
var res = await _db.QueryFirst<PKSystem>(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)

View File

@@ -6,10 +6,12 @@ namespace PluralKit.Core
{
private readonly ILogger _logger;
private readonly IDatabase _db;
public ModelRepository(ILogger logger, IDatabase db)
private readonly DispatchService _dispatch;
public ModelRepository(ILogger logger, IDatabase db, DispatchService dispatch)
{
_logger = logger.ForContext<ModelRepository>();
_db = db;
_dispatch = dispatch;
}
}
}

View File

@@ -12,7 +12,7 @@ namespace PluralKit.Core
internal class DatabaseMigrator
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 19;
private const int TargetSchemaVersion = 20;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)