feat(webhooks): SUCCESSFUL_IMPORT event, better behaviour when creating entities
This commit is contained in:
		@@ -22,12 +22,14 @@ namespace PluralKit.API
 | 
				
			|||||||
        protected readonly ApiConfig _config;
 | 
					        protected readonly ApiConfig _config;
 | 
				
			||||||
        protected readonly IDatabase _db;
 | 
					        protected readonly IDatabase _db;
 | 
				
			||||||
        protected readonly ModelRepository _repo;
 | 
					        protected readonly ModelRepository _repo;
 | 
				
			||||||
 | 
					        protected readonly DispatchService _dispatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public PKControllerBase(IServiceProvider svc)
 | 
					        public PKControllerBase(IServiceProvider svc)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _config = svc.GetRequiredService<ApiConfig>();
 | 
					            _config = svc.GetRequiredService<ApiConfig>();
 | 
				
			||||||
            _db = svc.GetRequiredService<IDatabase>();
 | 
					            _db = svc.GetRequiredService<IDatabase>();
 | 
				
			||||||
            _repo = svc.GetRequiredService<ModelRepository>();
 | 
					            _repo = svc.GetRequiredService<ModelRepository>();
 | 
				
			||||||
 | 
					            _dispatch = svc.GetRequiredService<DispatchService>();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        protected Task<PKSystem?> ResolveSystem(string systemRef)
 | 
					        protected Task<PKSystem?> ResolveSystem(string systemRef)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,6 +69,14 @@ namespace PluralKit.API
 | 
				
			|||||||
            var newGroup = await _repo.CreateGroup(system.Id, patch.Name.Value, conn);
 | 
					            var newGroup = await _repo.CreateGroup(system.Id, patch.Name.Value, conn);
 | 
				
			||||||
            newGroup = await _repo.UpdateGroup(newGroup.Id, patch, conn);
 | 
					            newGroup = await _repo.UpdateGroup(newGroup.Id, patch, conn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Event = DispatchEvent.CREATE_GROUP,
 | 
				
			||||||
 | 
					                EventData = patch.ToJson(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await tx.CommitAsync();
 | 
					            await tx.CommitAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Ok(newGroup.ToJson(LookupContext.ByOwner));
 | 
					            return Ok(newGroup.ToJson(LookupContext.ByOwner));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -55,6 +55,12 @@ namespace PluralKit.API
 | 
				
			|||||||
            var newMember = await _repo.CreateMember(system.Id, patch.Name.Value, conn);
 | 
					            var newMember = await _repo.CreateMember(system.Id, patch.Name.Value, conn);
 | 
				
			||||||
            newMember = await _repo.UpdateMember(newMember.Id, patch, conn);
 | 
					            newMember = await _repo.UpdateMember(newMember.Id, patch, conn);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _ = _dispatch.Dispatch(newMember.Id, new()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Event = DispatchEvent.CREATE_MEMBER,
 | 
				
			||||||
 | 
					                EventData = patch.ToJson(),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await tx.CommitAsync();
 | 
					            await tx.CommitAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
 | 
					            return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,8 @@ using Dapper;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
using Humanizer;
 | 
					using Humanizer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					using Newtonsoft.Json.Linq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using NodaTime;
 | 
					using NodaTime;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
using Myriad.Builders;
 | 
					using Myriad.Builders;
 | 
				
			||||||
@@ -24,13 +26,15 @@ namespace PluralKit.Bot
 | 
				
			|||||||
        private readonly ModelRepository _repo;
 | 
					        private readonly ModelRepository _repo;
 | 
				
			||||||
        private readonly EmbedService _embeds;
 | 
					        private readonly EmbedService _embeds;
 | 
				
			||||||
        private readonly HttpClient _client;
 | 
					        private readonly HttpClient _client;
 | 
				
			||||||
 | 
					        private readonly DispatchService _dispatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Groups(IDatabase db, ModelRepository repo, EmbedService embeds, HttpClient client)
 | 
					        public Groups(IDatabase db, ModelRepository repo, EmbedService embeds, HttpClient client, DispatchService dispatch)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _db = db;
 | 
					            _db = db;
 | 
				
			||||||
            _repo = repo;
 | 
					            _repo = repo;
 | 
				
			||||||
            _embeds = embeds;
 | 
					            _embeds = embeds;
 | 
				
			||||||
            _client = client;
 | 
					            _client = client;
 | 
				
			||||||
 | 
					            _dispatch = dispatch;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task CreateGroup(Context ctx)
 | 
					        public async Task CreateGroup(Context ctx)
 | 
				
			||||||
@@ -59,6 +63,12 @@ namespace PluralKit.Bot
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            var newGroup = await _repo.CreateGroup(ctx.System.Id, groupName);
 | 
					            var newGroup = await _repo.CreateGroup(ctx.System.Id, groupName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            _ = _dispatch.Dispatch(newGroup.Id, new UpdateDispatchData()
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                Event = DispatchEvent.CREATE_GROUP,
 | 
				
			||||||
 | 
					                EventData = JObject.FromObject(new { name = groupName }),
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            var eb = new EmbedBuilder()
 | 
					            var eb = new EmbedBuilder()
 | 
				
			||||||
                .Description($"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:")
 | 
					                .Description($"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:")
 | 
				
			||||||
                .Field(new("View the group card", $"> pk;group **{newGroup.Reference()}**"))
 | 
					                .Field(new("View the group card", $"> pk;group **{newGroup.Reference()}**"))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,13 +21,15 @@ namespace PluralKit.Bot
 | 
				
			|||||||
        private readonly ModelRepository _repo;
 | 
					        private readonly ModelRepository _repo;
 | 
				
			||||||
        private readonly EmbedService _embeds;
 | 
					        private readonly EmbedService _embeds;
 | 
				
			||||||
        private readonly HttpClient _client;
 | 
					        private readonly HttpClient _client;
 | 
				
			||||||
 | 
					        private readonly DispatchService _dispatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public Member(EmbedService embeds, IDatabase db, ModelRepository repo, HttpClient client)
 | 
					        public Member(EmbedService embeds, IDatabase db, ModelRepository repo, HttpClient client, DispatchService dispatch)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _embeds = embeds;
 | 
					            _embeds = embeds;
 | 
				
			||||||
            _db = db;
 | 
					            _db = db;
 | 
				
			||||||
            _repo = repo;
 | 
					            _repo = repo;
 | 
				
			||||||
            _client = client;
 | 
					            _client = client;
 | 
				
			||||||
 | 
					            _dispatch = dispatch;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task NewMember(Context ctx)
 | 
					        public async Task NewMember(Context ctx)
 | 
				
			||||||
@@ -62,12 +64,20 @@ namespace PluralKit.Bot
 | 
				
			|||||||
            // Try to match an image attached to the message
 | 
					            // Try to match an image attached to the message
 | 
				
			||||||
            var avatarArg = ctx.Message.Attachments.FirstOrDefault();
 | 
					            var avatarArg = ctx.Message.Attachments.FirstOrDefault();
 | 
				
			||||||
            Exception imageMatchError = null;
 | 
					            Exception imageMatchError = null;
 | 
				
			||||||
 | 
					            bool sentDispatch = false;
 | 
				
			||||||
            if (avatarArg != null)
 | 
					            if (avatarArg != null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                try
 | 
					                try
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    await AvatarUtils.VerifyAvatarOrThrow(_client, avatarArg.Url);
 | 
					                    await AvatarUtils.VerifyAvatarOrThrow(_client, avatarArg.Url);
 | 
				
			||||||
                    await _repo.UpdateMember(member.Id, new MemberPatch { AvatarUrl = avatarArg.Url });
 | 
					                    await _db.Execute(conn => _repo.UpdateMember(member.Id, new MemberPatch { AvatarUrl = avatarArg.Url }, conn));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    _ = _dispatch.Dispatch(member.Id, new()
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        Event = DispatchEvent.CREATE_MEMBER,
 | 
				
			||||||
 | 
					                        EventData = JObject.FromObject(new { name = memberName, avatar_url = avatarArg.Url }),
 | 
				
			||||||
 | 
					                    });
 | 
				
			||||||
 | 
					                    sentDispatch = true;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                catch (Exception e)
 | 
					                catch (Exception e)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
@@ -75,6 +85,13 @@ namespace PluralKit.Bot
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (!sentDispatch)
 | 
				
			||||||
 | 
					                _ = _dispatch.Dispatch(member.Id, new()
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Event = DispatchEvent.CREATE_MEMBER,
 | 
				
			||||||
 | 
					                    EventData = JObject.FromObject(new { name = memberName }),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Send confirmation and space hint
 | 
					            // Send confirmation and space hint
 | 
				
			||||||
            await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");
 | 
					            await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");
 | 
				
			||||||
            // todo: move this to ModelRepository
 | 
					            // todo: move this to ModelRepository
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,11 +64,6 @@ namespace PluralKit.Core
 | 
				
			|||||||
                name = name
 | 
					                name = name
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
            var group = await _db.QueryFirst<PKGroup>(conn, query, extraSql: "returning *");
 | 
					            var group = await _db.QueryFirst<PKGroup>(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);
 | 
					            _logger.Information("Created group {GroupId} in system {SystemId}: {GroupName}", group.Id, system, name);
 | 
				
			||||||
            return group;
 | 
					            return group;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -78,11 +73,13 @@ namespace PluralKit.Core
 | 
				
			|||||||
            _logger.Information("Updated {GroupId}: {@GroupPatch}", id, patch);
 | 
					            _logger.Information("Updated {GroupId}: {@GroupPatch}", id, patch);
 | 
				
			||||||
            var query = patch.Apply(new Query("groups").Where("id", id));
 | 
					            var query = patch.Apply(new Query("groups").Where("id", id));
 | 
				
			||||||
            var group = await _db.QueryFirst<PKGroup>(conn, query, extraSql: "returning *");
 | 
					            var group = await _db.QueryFirst<PKGroup>(conn, query, extraSql: "returning *");
 | 
				
			||||||
            _ = _dispatch.Dispatch(id, new()
 | 
					
 | 
				
			||||||
            {
 | 
					            if (conn == null)
 | 
				
			||||||
                Event = DispatchEvent.UPDATE_GROUP,
 | 
					                _ = _dispatch.Dispatch(id, new()
 | 
				
			||||||
                EventData = patch.ToJson(),
 | 
					                {
 | 
				
			||||||
            });
 | 
					                    Event = DispatchEvent.UPDATE_GROUP,
 | 
				
			||||||
 | 
					                    EventData = patch.ToJson(),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            return group;
 | 
					            return group;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,11 +69,6 @@ namespace PluralKit.Core
 | 
				
			|||||||
            var member = await _db.QueryFirst<PKMember>(conn, query, "returning *");
 | 
					            var member = await _db.QueryFirst<PKMember>(conn, query, "returning *");
 | 
				
			||||||
            _logger.Information("Created {MemberId} in {SystemId}: {MemberName}",
 | 
					            _logger.Information("Created {MemberId} in {SystemId}: {MemberName}",
 | 
				
			||||||
                member.Id, systemId, memberName);
 | 
					                member.Id, systemId, memberName);
 | 
				
			||||||
            _ = _dispatch.Dispatch(member.Id, new()
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                Event = DispatchEvent.CREATE_MEMBER,
 | 
					 | 
				
			||||||
                EventData = JObject.FromObject(new { name = memberName }),
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return member;
 | 
					            return member;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -81,11 +76,13 @@ namespace PluralKit.Core
 | 
				
			|||||||
        {
 | 
					        {
 | 
				
			||||||
            _logger.Information("Updated {MemberId}: {@MemberPatch}", id, patch);
 | 
					            _logger.Information("Updated {MemberId}: {@MemberPatch}", id, patch);
 | 
				
			||||||
            var query = patch.Apply(new Query("members").Where("id", id));
 | 
					            var query = patch.Apply(new Query("members").Where("id", id));
 | 
				
			||||||
            _ = _dispatch.Dispatch(id, new()
 | 
					
 | 
				
			||||||
            {
 | 
					            if (conn == null)
 | 
				
			||||||
                Event = DispatchEvent.UPDATE_MEMBER,
 | 
					                _ = _dispatch.Dispatch(id, new()
 | 
				
			||||||
                EventData = patch.ToJson(),
 | 
					                {
 | 
				
			||||||
            });
 | 
					                    Event = DispatchEvent.UPDATE_MEMBER,
 | 
				
			||||||
 | 
					                    EventData = patch.ToJson(),
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            return _db.QueryFirst<PKMember>(conn, query, extraSql: "returning *");
 | 
					            return _db.QueryFirst<PKMember>(conn, query, extraSql: "returning *");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,7 @@ namespace PluralKit.Core
 | 
				
			|||||||
        UPDATE_SWITCH_MEMBERS,
 | 
					        UPDATE_SWITCH_MEMBERS,
 | 
				
			||||||
        DELETE_SWITCH,
 | 
					        DELETE_SWITCH,
 | 
				
			||||||
        DELETE_ALL_SWITCHES,
 | 
					        DELETE_ALL_SWITCHES,
 | 
				
			||||||
 | 
					        SUCCESSFUL_IMPORT,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public struct UpdateDispatchData
 | 
					    public struct UpdateDispatchData
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,12 +18,14 @@ namespace PluralKit.Core
 | 
				
			|||||||
        private readonly IDatabase _db;
 | 
					        private readonly IDatabase _db;
 | 
				
			||||||
        private readonly ModelRepository _repo;
 | 
					        private readonly ModelRepository _repo;
 | 
				
			||||||
        private readonly ILogger _logger;
 | 
					        private readonly ILogger _logger;
 | 
				
			||||||
 | 
					        private readonly DispatchService _dispatch;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public DataFileService(IDatabase db, ModelRepository repo, ILogger logger)
 | 
					        public DataFileService(IDatabase db, ModelRepository repo, ILogger logger, DispatchService dispatch)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            _db = db;
 | 
					            _db = db;
 | 
				
			||||||
            _repo = repo;
 | 
					            _repo = repo;
 | 
				
			||||||
            _logger = logger;
 | 
					            _logger = logger;
 | 
				
			||||||
 | 
					            _dispatch = dispatch;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        public async Task<JObject> ExportSystem(PKSystem system)
 | 
					        public async Task<JObject> ExportSystem(PKSystem system)
 | 
				
			||||||
@@ -72,7 +74,7 @@ namespace PluralKit.Core
 | 
				
			|||||||
            await using var conn = await _db.Obtain();
 | 
					            await using var conn = await _db.Obtain();
 | 
				
			||||||
            await using var tx = await conn.BeginTransactionAsync();
 | 
					            await using var tx = await conn.BeginTransactionAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return await BulkImporter.PerformImport(conn, tx, _repo, _logger, userId, system, importFile, confirmFunc);
 | 
					            return await BulkImporter.PerformImport(conn, tx, _repo, _logger, _dispatch, userId, system, importFile, confirmFunc);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,7 @@ namespace PluralKit.Core
 | 
				
			|||||||
        private ImportResultNew _result = new();
 | 
					        private ImportResultNew _result = new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        internal static async Task<ImportResultNew> PerformImport(IPKConnection conn, IPKTransaction tx, ModelRepository repo, ILogger logger,
 | 
					        internal static async Task<ImportResultNew> PerformImport(IPKConnection conn, IPKTransaction tx, ModelRepository repo, ILogger logger,
 | 
				
			||||||
            ulong userId, PKSystem? system, JObject importFile, Func<string, Task> confirmFunc)
 | 
					            DispatchService dispatch, ulong userId, PKSystem? system, JObject importFile, Func<string, Task> confirmFunc)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            await using var importer = new BulkImporter()
 | 
					            await using var importer = new BulkImporter()
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
@@ -82,6 +82,11 @@ namespace PluralKit.Core
 | 
				
			|||||||
                    throw new ImportException("File type is unknown.");
 | 
					                    throw new ImportException("File type is unknown.");
 | 
				
			||||||
                importer._result.Success = true;
 | 
					                importer._result.Success = true;
 | 
				
			||||||
                await tx.CommitAsync();
 | 
					                await tx.CommitAsync();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                _ = dispatch.Dispatch(system.Id, new UpdateDispatchData()
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    Event = DispatchEvent.SUCCESSFUL_IMPORT
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            catch (ImportException e)
 | 
					            catch (ImportException e)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,4 +54,5 @@ PluralKit will send invalid requests to your endpoint, with `PING` event type, o
 | 
				
			|||||||
|UPDATE_SWITCH|a switch was updated|[switch object](/api/models#switch-model) with only modified keys|
 | 
					|UPDATE_SWITCH|a switch was updated|[switch object](/api/models#switch-model) with only modified keys|
 | 
				
			||||||
|UPDATE_SWITCH_MEMBERS|the member list of a switch was updated|list of member IDs|
 | 
					|UPDATE_SWITCH_MEMBERS|the member list of a switch was updated|list of member IDs|
 | 
				
			||||||
|DELETE_SWITCH|a switch was deleted|null|old switch ID can be found in top-level `id` key|
 | 
					|DELETE_SWITCH|a switch was deleted|null|old switch ID can be found in top-level `id` key|
 | 
				
			||||||
|DELETE_ALL_SWITCHES|your system's switches were bulk deleted|null|
 | 
					|DELETE_ALL_SWITCHES|your system's switches were bulk deleted|null|
 | 
				
			||||||
 | 
					|SUCCESSFUL_IMPORT|some information was successfully imported through the `pk;import` command to your system|null|
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user