Refactor member updates to use a patch object
This commit is contained in:
		| @@ -57,6 +57,7 @@ namespace PluralKit.API | ||||
|                 return BadRequest(e.Message); | ||||
|             } | ||||
|              | ||||
|             // TODO: retire SaveMember | ||||
|             await _data.SaveMember(member); | ||||
|             return Ok(member.ToJson(User.ContextFor(member))); | ||||
|         } | ||||
| @@ -80,6 +81,7 @@ namespace PluralKit.API | ||||
|                 return BadRequest(e.Message); | ||||
|             } | ||||
|              | ||||
|             // TODO: retire SaveMember | ||||
|             await _data.SaveMember(member); | ||||
|             return Ok(member.ToJson(User.ContextFor(member))); | ||||
|         } | ||||
|   | ||||
| @@ -7,6 +7,8 @@ using Dapper; | ||||
|  | ||||
| using DSharpPlus.Entities; | ||||
|  | ||||
| using NodaTime; | ||||
|  | ||||
| using PluralKit.Core; | ||||
|  | ||||
| namespace PluralKit.Bot | ||||
| @@ -40,8 +42,8 @@ namespace PluralKit.Bot | ||||
|             } | ||||
|  | ||||
|             // Rename the member | ||||
|             target.Name = newName; | ||||
|             await _data.SaveMember(target); | ||||
|             var patch = new MemberPatch {Name = Partial<string>.Present(newName)}; | ||||
|             await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|  | ||||
|             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."); | ||||
| @@ -67,9 +69,9 @@ namespace PluralKit.Bot | ||||
|             if (MatchClear(ctx)) | ||||
|             { | ||||
|                 CheckEditMemberPermission(ctx, target); | ||||
|                 target.Description = null; | ||||
|  | ||||
|                 await _data.SaveMember(target); | ||||
|                 var patch = new MemberPatch {Description = Partial<string>.Null()}; | ||||
|                 await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|                 await ctx.Reply($"{Emojis.Success} Member description cleared."); | ||||
|             }  | ||||
|             else if (!ctx.HasNext()) | ||||
| @@ -100,7 +102,8 @@ namespace PluralKit.Bot | ||||
|                     throw Errors.DescriptionTooLongError(description.Length); | ||||
|                 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."); | ||||
|             } | ||||
|         } | ||||
| @@ -109,9 +112,8 @@ namespace PluralKit.Bot | ||||
|             if (MatchClear(ctx)) | ||||
|             { | ||||
|                 CheckEditMemberPermission(ctx, target); | ||||
|                 target.Pronouns = null; | ||||
|                  | ||||
|                 await _data.SaveMember(target); | ||||
|                 var patch = new MemberPatch {Pronouns = Partial<string>.Null()}; | ||||
|                 await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|                 await ctx.Reply($"{Emojis.Success} Member pronouns cleared."); | ||||
|             }  | ||||
|             else if (!ctx.HasNext()) | ||||
| @@ -134,9 +136,10 @@ namespace PluralKit.Bot | ||||
|                 var pronouns = ctx.RemainderOrNull().NormalizeLineEndSpacing(); | ||||
|                 if (pronouns.IsLongerThan(Limits.MaxPronounsLength)) | ||||
|                     throw Errors.MemberPronounsTooLongError(pronouns.Length); | ||||
|                 target.Pronouns = pronouns; | ||||
|                  | ||||
|                 await _data.SaveMember(target); | ||||
|                 var patch = new MemberPatch {Pronouns = Partial<string>.Present(pronouns)}; | ||||
|                 await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|                  | ||||
|                 await ctx.Reply($"{Emojis.Success} Member pronouns changed."); | ||||
|             } | ||||
|         } | ||||
| @@ -147,8 +150,10 @@ namespace PluralKit.Bot | ||||
|             if (MatchClear(ctx)) | ||||
|             { | ||||
|                 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."); | ||||
|             } | ||||
|             else if (!ctx.HasNext()) | ||||
| @@ -177,12 +182,13 @@ namespace PluralKit.Bot | ||||
|  | ||||
|                 if (color.StartsWith("#")) color = color.Substring(1); | ||||
|                 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() | ||||
|                     .WithTitle($"{Emojis.Success} Member color changed.") | ||||
|                     .WithColor(target.Color.ToDiscordColor().Value) | ||||
|                     .WithColor(color.ToDiscordColor().Value) | ||||
|                     .WithThumbnail($"https://fakeimg.pl/256x256/{target.Color}/?text=%20") | ||||
|                     .Build()); | ||||
|             } | ||||
| @@ -192,8 +198,10 @@ namespace PluralKit.Bot | ||||
|             if (MatchClear(ctx)) | ||||
|             { | ||||
|                 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."); | ||||
|             }  | ||||
|             else if (!ctx.HasNext()) | ||||
| @@ -215,8 +223,10 @@ namespace PluralKit.Bot | ||||
|                 var birthdayStr = ctx.RemainderOrNull(); | ||||
|                 var birthday = DateUtils.ParseDate(birthdayStr, true); | ||||
|                 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."); | ||||
|             } | ||||
|         } | ||||
| @@ -275,8 +285,9 @@ namespace PluralKit.Bot | ||||
|             { | ||||
|                 CheckEditMemberPermission(ctx, target); | ||||
|                  | ||||
|                 target.DisplayName = null; | ||||
|                 await _data.SaveMember(target); | ||||
|                 var patch = new MemberPatch {DisplayName = Partial<string>.Null()}; | ||||
|                 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)}\"."); | ||||
|             } | ||||
|             else if (!ctx.HasNext()) | ||||
| @@ -292,8 +303,9 @@ namespace PluralKit.Bot | ||||
|                 CheckEditMemberPermission(ctx, target); | ||||
|                  | ||||
|                 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}\"."); | ||||
|             } | ||||
| @@ -356,8 +368,8 @@ namespace PluralKit.Bot | ||||
|                 return; | ||||
|             }; | ||||
|  | ||||
|             target.KeepProxy = newValue; | ||||
|             await _data.SaveMember(target); | ||||
|             var patch = new MemberPatch {KeepProxy = Partial<bool>.Present(newValue)}; | ||||
|             await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|              | ||||
|             if (newValue) | ||||
|                 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()); | ||||
|                  | ||||
|                 // Set the level on the given subject | ||||
|                 target.SetPrivacy(subject, newLevel); | ||||
|                 await _data.SaveMember(target); | ||||
|                 var patch = new MemberPatch(); | ||||
|                 patch.SetPrivacy(subject, newLevel); | ||||
|                 await _db.Execute(conn => conn.UpdateMember(target.Id, patch)); | ||||
|  | ||||
|                 // Print response | ||||
|                 var explanation = (subject, newLevel) switch | ||||
| @@ -460,8 +473,10 @@ namespace PluralKit.Bot | ||||
|             else if (ctx.Match("all") || newValueFromCommand != null) | ||||
|             { | ||||
|                 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) | ||||
|                     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!***__"); | ||||
|             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."); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -1,17 +1,19 @@ | ||||
| using System.Linq; | ||||
| using System.Threading.Tasks; | ||||
|  | ||||
| using Dapper; | ||||
|  | ||||
| using PluralKit.Core; | ||||
|  | ||||
| namespace PluralKit.Bot | ||||
| { | ||||
|     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) | ||||
| @@ -30,9 +32,9 @@ namespace PluralKit.Bot | ||||
|              | ||||
|             async Task<bool> WarnOnConflict(ProxyTag newTag) | ||||
|             { | ||||
|                 var conflicts = (await _data.GetConflictingProxies(ctx.System, newTag)) | ||||
|                     .Where(m => m.Id != target.Id) | ||||
|                     .ToList(); | ||||
|                 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"; | ||||
|                 var conflicts = (await _db.Execute(conn => conn.QueryAsync<PKMember>(query, | ||||
|                     new {Prefix = newTag.Prefix, Suffix = newTag.Suffix, Existing = target.Id}))).ToList(); | ||||
|                  | ||||
|                 if (conflicts.Count <= 0) return true; | ||||
|  | ||||
| @@ -56,7 +58,9 @@ namespace PluralKit.Bot | ||||
|                  | ||||
|                 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."); | ||||
|             } | ||||
|             // "Sub"command: no arguments; will print proxy tags | ||||
| @@ -83,11 +87,11 @@ namespace PluralKit.Bot | ||||
|                 if (!await WarnOnConflict(tagToAdd)) | ||||
|                     throw Errors.GenericCancelled(); | ||||
|  | ||||
|                 // It's not guaranteed the list's mutable, so we force it to be | ||||
|                 target.ProxyTags = target.ProxyTags.ToList(); | ||||
|                 target.ProxyTags.Add(tagToAdd); | ||||
|                 var newTags = target.ProxyTags.ToList(); | ||||
|                 newTags.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}`."); | ||||
|             } | ||||
|             // Subcommand: "remove" | ||||
| @@ -100,11 +104,11 @@ namespace PluralKit.Bot | ||||
|                 if (!target.ProxyTags.Contains(tagToRemove)) | ||||
|                     throw Errors.ProxyTagDoesNotExist(tagToRemove, target); | ||||
|  | ||||
|                 // It's not guaranteed the list's mutable, so we force it to be | ||||
|                 target.ProxyTags = target.ProxyTags.ToList(); | ||||
|                 target.ProxyTags.Remove(tagToRemove); | ||||
|                 var newTags = target.ProxyTags.ToList(); | ||||
|                 newTags.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}`."); | ||||
|             } | ||||
|             // Subcommand: bare proxy tag given | ||||
| @@ -125,9 +129,10 @@ namespace PluralKit.Bot | ||||
|                 if (!await WarnOnConflict(requestedTag)) | ||||
|                     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}`."); | ||||
|             } | ||||
|         } | ||||
|   | ||||
| @@ -3,6 +3,8 @@ using System.Threading.Tasks; | ||||
|  | ||||
| using Dapper; | ||||
|  | ||||
| using PluralKit.Core; | ||||
|  | ||||
| namespace PluralKit.Core | ||||
| { | ||||
|     public static class ModelQueryExt | ||||
| @@ -26,5 +28,16 @@ namespace PluralKit.Core | ||||
|             conn.QueryFirstAsync<MemberGuildSettings>( | ||||
|                 "insert into member_guild (guild, member) values (@guild, @member) on conflict (guild, member) do update set guild = @guild, member = @member returning *", | ||||
|                 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); | ||||
|     } | ||||
| } | ||||
| @@ -81,16 +81,6 @@ namespace PluralKit.Core { | ||||
|         /// <param name="includePrivate">Whether the returned count should include private members.</param> | ||||
|         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> | ||||
|         /// Creates a system, auto-generating its corresponding IDs. | ||||
|         /// </summary> | ||||
| @@ -127,12 +117,6 @@ namespace PluralKit.Core { | ||||
|         /// </para> | ||||
|         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> | ||||
|         /// Gets a member by its user-facing human ID. | ||||
|         /// </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}") | ||||
|         }; | ||||
|  | ||||
|         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 >.> | ||||
|             _ = subject switch | ||||
|             { | ||||
|                 MemberPrivacySubject.Name => member.NamePrivacy = level, | ||||
|                 MemberPrivacySubject.Description => member.DescriptionPrivacy = level, | ||||
|                 MemberPrivacySubject.Avatar => member.AvatarPrivacy = level, | ||||
|                 MemberPrivacySubject.Pronouns => member.PronounPrivacy = level, | ||||
|                 MemberPrivacySubject.Birthday => member.BirthdayPrivacy= level, | ||||
|                 MemberPrivacySubject.Metadata => member.MetadataPrivacy = level, | ||||
|                 MemberPrivacySubject.Visibility => member.MemberVisibility = level, | ||||
|                 MemberPrivacySubject.Name => member.NamePrivacy = Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Description => member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Avatar => member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Pronouns => member.PronounPrivacy = Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Birthday => member.BirthdayPrivacy= Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Metadata => member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level), | ||||
|                 MemberPrivacySubject.Visibility => member.Visibility = Partial<PrivacyLevel>.Present(level), | ||||
|                 _ => 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.DescriptionPrivacy = level; | ||||
|             member.AvatarPrivacy = level; | ||||
|             member.PronounPrivacy = level; | ||||
|             member.BirthdayPrivacy = level; | ||||
|             member.MetadataPrivacy = level; | ||||
|             member.MemberVisibility = level; | ||||
|             member.NamePrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.DescriptionPrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.AvatarPrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.PronounPrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.BirthdayPrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.MetadataPrivacy = Partial<PrivacyLevel>.Present(level); | ||||
|             member.Visibility = Partial<PrivacyLevel>.Present(level); | ||||
|         } | ||||
|          | ||||
|         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); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user