Refactor member updates to use a patch object
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -80,16 +80,6 @@ namespace PluralKit.Core {
|
||||
/// </summary>
|
||||
/// <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.
|
||||
@@ -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