Refactor member updates to use a patch object
This commit is contained in:
parent
472e556ef0
commit
281b669391
@ -57,6 +57,7 @@ namespace PluralKit.API
|
|||||||
return BadRequest(e.Message);
|
return BadRequest(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: retire SaveMember
|
||||||
await _data.SaveMember(member);
|
await _data.SaveMember(member);
|
||||||
return Ok(member.ToJson(User.ContextFor(member)));
|
return Ok(member.ToJson(User.ContextFor(member)));
|
||||||
}
|
}
|
||||||
@ -80,6 +81,7 @@ namespace PluralKit.API
|
|||||||
return BadRequest(e.Message);
|
return BadRequest(e.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: retire SaveMember
|
||||||
await _data.SaveMember(member);
|
await _data.SaveMember(member);
|
||||||
return Ok(member.ToJson(User.ContextFor(member)));
|
return Ok(member.ToJson(User.ContextFor(member)));
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ using Dapper;
|
|||||||
|
|
||||||
using DSharpPlus.Entities;
|
using DSharpPlus.Entities;
|
||||||
|
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Bot
|
namespace PluralKit.Bot
|
||||||
@ -40,8 +42,8 @@ namespace PluralKit.Bot
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Rename the member
|
// Rename the member
|
||||||
target.Name = newName;
|
var patch = new MemberPatch {Name = Partial<string>.Present(newName)};
|
||||||
await _data.SaveMember(target);
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member renamed.");
|
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.");
|
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.");
|
||||||
@ -67,9 +69,9 @@ namespace PluralKit.Bot
|
|||||||
if (MatchClear(ctx))
|
if (MatchClear(ctx))
|
||||||
{
|
{
|
||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
target.Description = null;
|
|
||||||
|
var patch = new MemberPatch {Description = Partial<string>.Null()};
|
||||||
await _data.SaveMember(target);
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
await ctx.Reply($"{Emojis.Success} Member description cleared.");
|
await ctx.Reply($"{Emojis.Success} Member description cleared.");
|
||||||
}
|
}
|
||||||
else if (!ctx.HasNext())
|
else if (!ctx.HasNext())
|
||||||
@ -100,7 +102,8 @@ namespace PluralKit.Bot
|
|||||||
throw Errors.DescriptionTooLongError(description.Length);
|
throw Errors.DescriptionTooLongError(description.Length);
|
||||||
target.Description = description;
|
target.Description = description;
|
||||||
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {Description = Partial<string>.Present(description)};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
await ctx.Reply($"{Emojis.Success} Member description changed.");
|
await ctx.Reply($"{Emojis.Success} Member description changed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,9 +112,8 @@ namespace PluralKit.Bot
|
|||||||
if (MatchClear(ctx))
|
if (MatchClear(ctx))
|
||||||
{
|
{
|
||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
target.Pronouns = null;
|
var patch = new MemberPatch {Pronouns = Partial<string>.Null()};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
await _data.SaveMember(target);
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member pronouns cleared.");
|
await ctx.Reply($"{Emojis.Success} Member pronouns cleared.");
|
||||||
}
|
}
|
||||||
else if (!ctx.HasNext())
|
else if (!ctx.HasNext())
|
||||||
@ -134,9 +136,10 @@ namespace PluralKit.Bot
|
|||||||
var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing();
|
var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing();
|
||||||
if (pronouns.IsLongerThan(Limits.MaxPronounsLength))
|
if (pronouns.IsLongerThan(Limits.MaxPronounsLength))
|
||||||
throw Errors.MemberPronounsTooLongError(pronouns.Length);
|
throw Errors.MemberPronounsTooLongError(pronouns.Length);
|
||||||
target.Pronouns = pronouns;
|
|
||||||
|
var patch = new MemberPatch {Pronouns = Partial<string>.Present(pronouns)};
|
||||||
await _data.SaveMember(target);
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member pronouns changed.");
|
await ctx.Reply($"{Emojis.Success} Member pronouns changed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -147,8 +150,10 @@ namespace PluralKit.Bot
|
|||||||
if (MatchClear(ctx))
|
if (MatchClear(ctx))
|
||||||
{
|
{
|
||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
target.Color = null;
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {Color = Partial<string>.Null()};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member color cleared.");
|
await ctx.Reply($"{Emojis.Success} Member color cleared.");
|
||||||
}
|
}
|
||||||
else if (!ctx.HasNext())
|
else if (!ctx.HasNext())
|
||||||
@ -177,12 +182,13 @@ namespace PluralKit.Bot
|
|||||||
|
|
||||||
if (color.StartsWith("#")) color = color.Substring(1);
|
if (color.StartsWith("#")) color = color.Substring(1);
|
||||||
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
if (!Regex.IsMatch(color, "^[0-9a-fA-F]{6}$")) throw Errors.InvalidColorError(color);
|
||||||
target.Color = color.ToLower();
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {Color = Partial<string>.Present(color.ToLowerInvariant())};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply(embed: new DiscordEmbedBuilder()
|
await ctx.Reply(embed: new DiscordEmbedBuilder()
|
||||||
.WithTitle($"{Emojis.Success} Member color changed.")
|
.WithTitle($"{Emojis.Success} Member color changed.")
|
||||||
.WithColor(target.Color.ToDiscordColor().Value)
|
.WithColor(color.ToDiscordColor().Value)
|
||||||
.WithThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")
|
.WithThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20")
|
||||||
.Build());
|
.Build());
|
||||||
}
|
}
|
||||||
@ -192,8 +198,10 @@ namespace PluralKit.Bot
|
|||||||
if (MatchClear(ctx))
|
if (MatchClear(ctx))
|
||||||
{
|
{
|
||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
target.Birthday = null;
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {Birthday = Partial<LocalDate?>.Null()};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member birthdate cleared.");
|
await ctx.Reply($"{Emojis.Success} Member birthdate cleared.");
|
||||||
}
|
}
|
||||||
else if (!ctx.HasNext())
|
else if (!ctx.HasNext())
|
||||||
@ -215,8 +223,10 @@ namespace PluralKit.Bot
|
|||||||
var birthdayStr = ctx.RemainderOrNull();
|
var birthdayStr = ctx.RemainderOrNull();
|
||||||
var birthday = DateUtils.ParseDate(birthdayStr, true);
|
var birthday = DateUtils.ParseDate(birthdayStr, true);
|
||||||
if (birthday == null) throw Errors.BirthdayParseError(birthdayStr);
|
if (birthday == null) throw Errors.BirthdayParseError(birthdayStr);
|
||||||
target.Birthday = birthday;
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {Birthday = Partial<LocalDate?>.Present(birthday)};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member birthdate changed.");
|
await ctx.Reply($"{Emojis.Success} Member birthdate changed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -275,8 +285,9 @@ namespace PluralKit.Bot
|
|||||||
{
|
{
|
||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
|
|
||||||
target.DisplayName = null;
|
var patch = new MemberPatch {DisplayName = Partial<string>.Null()};
|
||||||
await _data.SaveMember(target);
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await PrintSuccess($"{Emojis.Success} Member display name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\".");
|
await PrintSuccess($"{Emojis.Success} Member display name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\".");
|
||||||
}
|
}
|
||||||
else if (!ctx.HasNext())
|
else if (!ctx.HasNext())
|
||||||
@ -292,8 +303,9 @@ namespace PluralKit.Bot
|
|||||||
CheckEditMemberPermission(ctx, target);
|
CheckEditMemberPermission(ctx, target);
|
||||||
|
|
||||||
var newDisplayName = ctx.RemainderOrNull();
|
var newDisplayName = ctx.RemainderOrNull();
|
||||||
target.DisplayName = newDisplayName;
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {DisplayName = Partial<string>.Present(newDisplayName)};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\".");
|
await PrintSuccess($"{Emojis.Success} Member display name changed. This member will now be proxied using the name \"{newDisplayName}\".");
|
||||||
}
|
}
|
||||||
@ -356,8 +368,8 @@ namespace PluralKit.Bot
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
target.KeepProxy = newValue;
|
var patch = new MemberPatch {KeepProxy = Partial<bool>.Present(newValue)};
|
||||||
await _data.SaveMember(target);
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
if (newValue)
|
if (newValue)
|
||||||
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
|
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
|
||||||
@ -430,8 +442,9 @@ namespace PluralKit.Bot
|
|||||||
newLevel = PopPrivacyLevel(subject.Name());
|
newLevel = PopPrivacyLevel(subject.Name());
|
||||||
|
|
||||||
// Set the level on the given subject
|
// Set the level on the given subject
|
||||||
target.SetPrivacy(subject, newLevel);
|
var patch = new MemberPatch();
|
||||||
await _data.SaveMember(target);
|
patch.SetPrivacy(subject, newLevel);
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
// Print response
|
// Print response
|
||||||
var explanation = (subject, newLevel) switch
|
var explanation = (subject, newLevel) switch
|
||||||
@ -460,8 +473,10 @@ namespace PluralKit.Bot
|
|||||||
else if (ctx.Match("all") || newValueFromCommand != null)
|
else if (ctx.Match("all") || newValueFromCommand != null)
|
||||||
{
|
{
|
||||||
newLevel = newValueFromCommand ?? PopPrivacyLevel("all");
|
newLevel = newValueFromCommand ?? PopPrivacyLevel("all");
|
||||||
target.SetAllPrivacy(newLevel);
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch();
|
||||||
|
patch.SetAllPrivacy(newLevel);
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
if(newLevel == PrivacyLevel.Private)
|
if(newLevel == PrivacyLevel.Private)
|
||||||
await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see nothing on the member card.");
|
await ctx.Reply($"All {target.NameFor(ctx)}'s privacy settings have been set to **{newLevel.LevelName()}**. Other accounts will now see nothing on the member card.");
|
||||||
@ -490,7 +505,9 @@ namespace PluralKit.Bot
|
|||||||
|
|
||||||
await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.NameFor(ctx)}\"? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__");
|
await ctx.Reply($"{Emojis.Warn} Are you sure you want to delete \"{target.NameFor(ctx)}\"? 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;
|
if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled;
|
||||||
await _data.DeleteMember(target);
|
|
||||||
|
await _db.Execute(conn => conn.DeleteMember(target.Id));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member deleted.");
|
await ctx.Reply($"{Emojis.Success} Member deleted.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,19 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
using PluralKit.Core;
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Bot
|
namespace PluralKit.Bot
|
||||||
{
|
{
|
||||||
public class MemberProxy
|
public class MemberProxy
|
||||||
{
|
{
|
||||||
private IDataStore _data;
|
private readonly IDatabase _db;
|
||||||
|
|
||||||
public MemberProxy(IDataStore data)
|
public MemberProxy(IDatabase db)
|
||||||
{
|
{
|
||||||
_data = data;
|
_db = db;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Proxy(Context ctx, PKMember target)
|
public async Task Proxy(Context ctx, PKMember target)
|
||||||
@ -30,10 +32,10 @@ namespace PluralKit.Bot
|
|||||||
|
|
||||||
async Task<bool> WarnOnConflict(ProxyTag newTag)
|
async Task<bool> WarnOnConflict(ProxyTag newTag)
|
||||||
{
|
{
|
||||||
var conflicts = (await _data.GetConflictingProxies(ctx.System, newTag))
|
var query = "select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix = @Prefix and suffix = @Suffix and id != @Existing";
|
||||||
.Where(m => m.Id != target.Id)
|
var conflicts = (await _db.Execute(conn => conn.QueryAsync<PKMember>(query,
|
||||||
.ToList();
|
new {Prefix = newTag.Prefix, Suffix = newTag.Suffix, Existing = target.Id}))).ToList();
|
||||||
|
|
||||||
if (conflicts.Count <= 0) return true;
|
if (conflicts.Count <= 0) return true;
|
||||||
|
|
||||||
var conflictList = conflicts.Select(m => $"- **{m.NameFor(ctx)}**");
|
var conflictList = conflicts.Select(m => $"- **{m.NameFor(ctx)}**");
|
||||||
@ -56,7 +58,9 @@ namespace PluralKit.Bot
|
|||||||
|
|
||||||
target.ProxyTags = new ProxyTag[] { };
|
target.ProxyTags = new ProxyTag[] { };
|
||||||
|
|
||||||
await _data.SaveMember(target);
|
var patch = new MemberPatch {ProxyTags = Partial<ProxyTag[]>.Present(new ProxyTag[0])};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
|
await ctx.Reply($"{Emojis.Success} Proxy tags cleared.");
|
||||||
}
|
}
|
||||||
// "Sub"command: no arguments; will print proxy tags
|
// "Sub"command: no arguments; will print proxy tags
|
||||||
@ -83,11 +87,11 @@ namespace PluralKit.Bot
|
|||||||
if (!await WarnOnConflict(tagToAdd))
|
if (!await WarnOnConflict(tagToAdd))
|
||||||
throw Errors.GenericCancelled();
|
throw Errors.GenericCancelled();
|
||||||
|
|
||||||
// It's not guaranteed the list's mutable, so we force it to be
|
var newTags = target.ProxyTags.ToList();
|
||||||
target.ProxyTags = target.ProxyTags.ToList();
|
newTags.Add(tagToAdd);
|
||||||
target.ProxyTags.Add(tagToAdd);
|
var patch = new MemberPatch {ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray())};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
await _data.SaveMember(target);
|
|
||||||
await ctx.Reply($"{Emojis.Success} Added proxy tags `{tagToAdd.ProxyString}`.");
|
await ctx.Reply($"{Emojis.Success} Added proxy tags `{tagToAdd.ProxyString}`.");
|
||||||
}
|
}
|
||||||
// Subcommand: "remove"
|
// Subcommand: "remove"
|
||||||
@ -100,11 +104,11 @@ namespace PluralKit.Bot
|
|||||||
if (!target.ProxyTags.Contains(tagToRemove))
|
if (!target.ProxyTags.Contains(tagToRemove))
|
||||||
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
|
throw Errors.ProxyTagDoesNotExist(tagToRemove, target);
|
||||||
|
|
||||||
// It's not guaranteed the list's mutable, so we force it to be
|
var newTags = target.ProxyTags.ToList();
|
||||||
target.ProxyTags = target.ProxyTags.ToList();
|
newTags.Remove(tagToRemove);
|
||||||
target.ProxyTags.Remove(tagToRemove);
|
var patch = new MemberPatch {ProxyTags = Partial<ProxyTag[]>.Present(newTags.ToArray())};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
await _data.SaveMember(target);
|
|
||||||
await ctx.Reply($"{Emojis.Success} Removed proxy tags `{tagToRemove.ProxyString}`.");
|
await ctx.Reply($"{Emojis.Success} Removed proxy tags `{tagToRemove.ProxyString}`.");
|
||||||
}
|
}
|
||||||
// Subcommand: bare proxy tag given
|
// Subcommand: bare proxy tag given
|
||||||
@ -125,9 +129,10 @@ namespace PluralKit.Bot
|
|||||||
if (!await WarnOnConflict(requestedTag))
|
if (!await WarnOnConflict(requestedTag))
|
||||||
throw Errors.GenericCancelled();
|
throw Errors.GenericCancelled();
|
||||||
|
|
||||||
target.ProxyTags = new[] {requestedTag};
|
var newTags = new[] {requestedTag};
|
||||||
|
var patch = new MemberPatch {ProxyTags = Partial<ProxyTag[]>.Present(newTags)};
|
||||||
|
await _db.Execute(conn => conn.UpdateMember(target.Id, patch));
|
||||||
|
|
||||||
await _data.SaveMember(target);
|
|
||||||
await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString}`.");
|
await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString}`.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ using System.Threading.Tasks;
|
|||||||
|
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
|
||||||
|
using PluralKit.Core;
|
||||||
|
|
||||||
namespace PluralKit.Core
|
namespace PluralKit.Core
|
||||||
{
|
{
|
||||||
public static class ModelQueryExt
|
public static class ModelQueryExt
|
||||||
@ -26,5 +28,16 @@ namespace PluralKit.Core
|
|||||||
conn.QueryFirstAsync<MemberGuildSettings>(
|
conn.QueryFirstAsync<MemberGuildSettings>(
|
||||||
"insert into member_guild (guild, member) values (@guild, @member) on conflict (guild, member) do update set guild = @guild, member = @member returning *",
|
"insert into member_guild (guild, member) values (@guild, @member) on conflict (guild, member) do update set guild = @guild, member = @member returning *",
|
||||||
new {guild, member});
|
new {guild, member});
|
||||||
|
|
||||||
|
public static Task<PKMember> UpdateMember(this IPKConnection conn, MemberId id, MemberPatch patch)
|
||||||
|
{
|
||||||
|
var (query, pms) = patch.Apply(new UpdateQueryBuilder("members", "id = @id"))
|
||||||
|
.WithConstant("id", id)
|
||||||
|
.Build("returning *");
|
||||||
|
return conn.QueryFirstAsync<PKMember>(query, pms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task DeleteMember(this IPKConnection conn, MemberId id) =>
|
||||||
|
conn.ExecuteAsync("delete from members where id = @Id", new {Id = id});
|
||||||
}
|
}
|
||||||
}
|
}
|
44
PluralKit.Core/Models/Patch/MemberPatch.cs
Normal file
44
PluralKit.Core/Models/Patch/MemberPatch.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using NodaTime;
|
||||||
|
|
||||||
|
namespace PluralKit.Core
|
||||||
|
{
|
||||||
|
public class MemberPatch: PatchObject<MemberId, PKMember>
|
||||||
|
{
|
||||||
|
public Partial<string> Name { get; set; }
|
||||||
|
public Partial<string?> DisplayName { get; set; }
|
||||||
|
public Partial<string?> Color { get; set; }
|
||||||
|
public Partial<LocalDate?> Birthday { get; set; }
|
||||||
|
public Partial<string?> Pronouns { get; set; }
|
||||||
|
public Partial<string?> Description { get; set; }
|
||||||
|
public Partial<ProxyTag[]> ProxyTags { get; set; }
|
||||||
|
public Partial<bool> KeepProxy { get; set; }
|
||||||
|
public Partial<int> MessageCount { get; set; }
|
||||||
|
public Partial<PrivacyLevel> Visibility { get; set; }
|
||||||
|
public Partial<PrivacyLevel> NamePrivacy { get; set; }
|
||||||
|
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
|
||||||
|
public Partial<PrivacyLevel> PronounPrivacy { get; set; }
|
||||||
|
public Partial<PrivacyLevel> BirthdayPrivacy { get; set; }
|
||||||
|
public Partial<PrivacyLevel> AvatarPrivacy { get; set; }
|
||||||
|
public Partial<PrivacyLevel> MetadataPrivacy { get; set; }
|
||||||
|
|
||||||
|
public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b
|
||||||
|
.With("name", Name)
|
||||||
|
.With("display_name", DisplayName)
|
||||||
|
.With("color", Color)
|
||||||
|
.With("birthday", Birthday)
|
||||||
|
.With("pronouns", Pronouns)
|
||||||
|
.With("description", Description)
|
||||||
|
.With("proxy_tags", ProxyTags)
|
||||||
|
.With("keep_proxy", KeepProxy)
|
||||||
|
.With("message_count", MessageCount)
|
||||||
|
.With("member_visibility", Visibility)
|
||||||
|
.With("name_privacy", NamePrivacy)
|
||||||
|
.With("description_privacy", DescriptionPrivacy)
|
||||||
|
.With("pronoun_privacy", PronounPrivacy)
|
||||||
|
.With("birthday_privacy", BirthdayPrivacy)
|
||||||
|
.With("avatar_privacy", AvatarPrivacy)
|
||||||
|
.With("metadata_privacy", MetadataPrivacy);
|
||||||
|
}
|
||||||
|
}
|
9
PluralKit.Core/Models/Patch/PatchObject.cs
Normal file
9
PluralKit.Core/Models/Patch/PatchObject.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
using PluralKit.Core;
|
||||||
|
|
||||||
|
namespace PluralKit.Core
|
||||||
|
{
|
||||||
|
public abstract class PatchObject<TKey, TObj>
|
||||||
|
{
|
||||||
|
public abstract UpdateQueryBuilder Apply(UpdateQueryBuilder b);
|
||||||
|
}
|
||||||
|
}
|
@ -80,16 +80,6 @@ namespace PluralKit.Core {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="includePrivate">Whether the returned count should include private members.</param>
|
/// <param name="includePrivate">Whether the returned count should include private members.</param>
|
||||||
Task<int> GetSystemMemberCount(SystemId system, bool includePrivate);
|
Task<int> GetSystemMemberCount(SystemId system, bool includePrivate);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a list of members with proxy tags that conflict with the given tags.
|
|
||||||
///
|
|
||||||
/// A set of proxy tags A conflict with proxy tags B if both A's prefix and suffix
|
|
||||||
/// are a "subset" of B's. In other words, if A's prefix *starts* with B's prefix
|
|
||||||
/// and A's suffix *ends* with B's suffix, the tag pairs are considered conflicting.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="system">The system to check in.</param>
|
|
||||||
Task<IEnumerable<PKMember>> GetConflictingProxies(PKSystem system, ProxyTag tag);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a system, auto-generating its corresponding IDs.
|
/// Creates a system, auto-generating its corresponding IDs.
|
||||||
@ -127,12 +117,6 @@ namespace PluralKit.Core {
|
|||||||
/// </para>
|
/// </para>
|
||||||
Task DeleteSystem(PKSystem system);
|
Task DeleteSystem(PKSystem system);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a system by its internal member ID.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The <see cref="PKMember"/> with the given internal ID, or null if no member was found.</returns>
|
|
||||||
Task<PKMember> GetMemberById(MemberId memberId);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a member by its user-facing human ID.
|
/// Gets a member by its user-facing human ID.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
80
PluralKit.Core/Utils/Partial.cs
Normal file
80
PluralKit.Core/Utils/Partial.cs
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace PluralKit.Core
|
||||||
|
{
|
||||||
|
[JsonConverter(typeof(PartialConverter))]
|
||||||
|
public struct Partial<T>: IEnumerable<T>
|
||||||
|
{
|
||||||
|
public bool IsPresent { get; }
|
||||||
|
public T Value { get; }
|
||||||
|
|
||||||
|
private Partial(bool isPresent, T value)
|
||||||
|
{
|
||||||
|
IsPresent = isPresent;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Partial<T> Null() => new Partial<T>(true, default!);
|
||||||
|
public static Partial<T> Present(T obj) => new Partial<T>(true, obj);
|
||||||
|
public static Partial<T> Absent = new Partial<T>(false, default!);
|
||||||
|
|
||||||
|
public IEnumerable<T> ToArray() => IsPresent ? new[] {Value} : new T[0];
|
||||||
|
|
||||||
|
public IEnumerator<T> GetEnumerator() => ToArray().GetEnumerator();
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => ToArray().GetEnumerator();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PartialConverter: JsonConverter
|
||||||
|
{
|
||||||
|
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue,
|
||||||
|
JsonSerializer serializer)
|
||||||
|
{
|
||||||
|
var innerType = objectType.GenericTypeArguments[0];
|
||||||
|
var innerValue = serializer.Deserialize(reader, innerType);
|
||||||
|
|
||||||
|
return typeof(Partial<>)
|
||||||
|
.MakeGenericType(innerType)
|
||||||
|
.GetMethod(nameof(Partial<object>.Present))!
|
||||||
|
.Invoke(null, new[] {innerValue});
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) =>
|
||||||
|
throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override bool CanConvert(Type objectType) => true;
|
||||||
|
|
||||||
|
public override bool CanRead => true;
|
||||||
|
public override bool CanWrite => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class PartialExt
|
||||||
|
{
|
||||||
|
public static bool TryGet<T>(this Partial<T> pt, out T value)
|
||||||
|
{
|
||||||
|
value = pt.IsPresent ? pt.Value : default!;
|
||||||
|
return pt.IsPresent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Or<T>(this Partial<T> pt, T fallback) => pt.IsPresent ? pt.Value : fallback;
|
||||||
|
public static T Or<T>(this Partial<T> pt, Func<T> fallback) => pt.IsPresent ? pt.Value : fallback.Invoke();
|
||||||
|
|
||||||
|
public static Partial<TOut> Map<TIn, TOut>(this Partial<TIn> pt, Func<TIn, TOut> fn) =>
|
||||||
|
pt.IsPresent ? Partial<TOut>.Present(fn.Invoke(pt.Value)) : Partial<TOut>.Absent;
|
||||||
|
|
||||||
|
public static void Apply<T>(this Partial<T> pt, DynamicParameters bag, QueryBuilder qb, string fieldName)
|
||||||
|
{
|
||||||
|
if (!pt.IsPresent) return;
|
||||||
|
|
||||||
|
bag.Add(fieldName, pt.Value);
|
||||||
|
qb.Variable(fieldName, $"@{fieldName}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -26,31 +26,31 @@ namespace PluralKit.Core
|
|||||||
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
|
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
|
||||||
};
|
};
|
||||||
|
|
||||||
public static void SetPrivacy(this PKMember member, MemberPrivacySubject subject, PrivacyLevel level)
|
public static void SetPrivacy(this MemberPatch member, MemberPrivacySubject subject, PrivacyLevel level)
|
||||||
{
|
{
|
||||||
// what do you mean switch expressions can't be statements >.>
|
// what do you mean switch expressions can't be statements >.>
|
||||||
_ = subject switch
|
_ = subject switch
|
||||||
{
|
{
|
||||||
MemberPrivacySubject.Name => member.NamePrivacy = level,
|
MemberPrivacySubject.Name => member.NamePrivacy = Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Description => member.DescriptionPrivacy = level,
|
MemberPrivacySubject.Description => member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Avatar => member.AvatarPrivacy = level,
|
MemberPrivacySubject.Avatar => member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Pronouns => member.PronounPrivacy = level,
|
MemberPrivacySubject.Pronouns => member.PronounPrivacy = Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Birthday => member.BirthdayPrivacy= level,
|
MemberPrivacySubject.Birthday => member.BirthdayPrivacy= Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Metadata => member.MetadataPrivacy = level,
|
MemberPrivacySubject.Metadata => member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level),
|
||||||
MemberPrivacySubject.Visibility => member.MemberVisibility = level,
|
MemberPrivacySubject.Visibility => member.Visibility = Partial<PrivacyLevel>.Present(level),
|
||||||
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
|
_ => throw new ArgumentOutOfRangeException($"Unknown privacy subject {subject}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetAllPrivacy(this PKMember member, PrivacyLevel level)
|
public static void SetAllPrivacy(this MemberPatch member, PrivacyLevel level)
|
||||||
{
|
{
|
||||||
member.NamePrivacy = level;
|
member.NamePrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.DescriptionPrivacy = level;
|
member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.AvatarPrivacy = level;
|
member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.PronounPrivacy = level;
|
member.PronounPrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.BirthdayPrivacy = level;
|
member.BirthdayPrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.MetadataPrivacy = level;
|
member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level);
|
||||||
member.MemberVisibility = level;
|
member.Visibility = Partial<PrivacyLevel>.Present(level);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool TryParseMemberPrivacy(string input, out MemberPrivacySubject subject)
|
public static bool TryParseMemberPrivacy(string input, out MemberPrivacySubject subject)
|
||||||
|
51
PluralKit.Core/Utils/UpdateQueryBuilder.cs
Normal file
51
PluralKit.Core/Utils/UpdateQueryBuilder.cs
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
using System.Text;
|
||||||
|
|
||||||
|
using Dapper;
|
||||||
|
|
||||||
|
namespace PluralKit.Core
|
||||||
|
{
|
||||||
|
public class UpdateQueryBuilder
|
||||||
|
{
|
||||||
|
private readonly string _table;
|
||||||
|
private readonly string _condition;
|
||||||
|
private readonly DynamicParameters _params = new DynamicParameters();
|
||||||
|
|
||||||
|
private bool _hasFields = false;
|
||||||
|
private readonly StringBuilder _setClause = new StringBuilder();
|
||||||
|
|
||||||
|
public UpdateQueryBuilder(string table, string condition)
|
||||||
|
{
|
||||||
|
_table = table;
|
||||||
|
_condition = condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateQueryBuilder WithConstant<T>(string name, T value)
|
||||||
|
{
|
||||||
|
_params.Add(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateQueryBuilder With<T>(string columnName, T value)
|
||||||
|
{
|
||||||
|
_params.Add(columnName, value);
|
||||||
|
|
||||||
|
if (_hasFields)
|
||||||
|
_setClause.Append(", ");
|
||||||
|
else _hasFields = true;
|
||||||
|
|
||||||
|
_setClause.Append($"{columnName} = @{columnName}");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateQueryBuilder With<T>(string columnName, Partial<T> partialValue)
|
||||||
|
{
|
||||||
|
return partialValue.IsPresent ? With(columnName, partialValue.Value) : this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public (string Query, DynamicParameters Parameters) Build(string append = "")
|
||||||
|
{
|
||||||
|
var query = $"update {_table} set {_setClause} where {_condition} {append}";
|
||||||
|
return (query, _params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user