Move system updates to the same patch system as members

This commit is contained in:
Ske 2020-06-29 14:39:19 +02:00
parent c5697b33e2
commit 9c1efc7886
12 changed files with 172 additions and 111 deletions

View File

@ -26,18 +26,20 @@ namespace PluralKit.API
return o;
}
public static void ApplyJson(this PKSystem system, JObject o)
public static SystemPatch ToSystemPatch(JObject o)
{
if (o.ContainsKey("name")) system.Name = o.Value<string>("name").NullIfEmpty().BoundsCheckField(Limits.MaxSystemNameLength, "System name");
if (o.ContainsKey("description")) system.Description = o.Value<string>("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "System description");
if (o.ContainsKey("tag")) system.Tag = o.Value<string>("tag").NullIfEmpty().BoundsCheckField(Limits.MaxSystemTagLength, "System tag");
if (o.ContainsKey("avatar_url")) system.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System avatar URL");
if (o.ContainsKey("tz")) system.UiTz = o.Value<string>("tz") ?? "UTC";
var patch = new SystemPatch();
if (o.ContainsKey("name")) patch.Name = o.Value<string>("name").NullIfEmpty().BoundsCheckField(Limits.MaxSystemNameLength, "System name");
if (o.ContainsKey("description")) patch.Description = o.Value<string>("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "System description");
if (o.ContainsKey("tag")) patch.Tag = o.Value<string>("tag").NullIfEmpty().BoundsCheckField(Limits.MaxSystemTagLength, "System tag");
if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System avatar URL");
if (o.ContainsKey("tz")) patch.UiTz = o.Value<string>("tz") ?? "UTC";
if (o.ContainsKey("description_privacy")) system.DescriptionPrivacy = o.Value<string>("description_privacy").ParsePrivacy("description");
if (o.ContainsKey("member_list_privacy")) system.MemberListPrivacy = o.Value<string>("member_list_privacy").ParsePrivacy("member list");
if (o.ContainsKey("front_privacy")) system.FrontPrivacy = o.Value<string>("front_privacy").ParsePrivacy("front");
if (o.ContainsKey("front_history_privacy")) system.FrontHistoryPrivacy = o.Value<string>("front_history_privacy").ParsePrivacy("front history");
if (o.ContainsKey("description_privacy")) patch.DescriptionPrivacy = o.Value<string>("description_privacy").ParsePrivacy("description");
if (o.ContainsKey("member_list_privacy")) patch.MemberListPrivacy = o.Value<string>("member_list_privacy").ParsePrivacy("member list");
if (o.ContainsKey("front_privacy")) patch.FrontPrivacy = o.Value<string>("front_privacy").ParsePrivacy("front");
if (o.ContainsKey("front_history_privacy")) patch.FrontHistoryPrivacy = o.Value<string>("front_history_privacy").ParsePrivacy("front history");
return patch;
}
public static JObject ToJson(this PKMember member, LookupContext ctx)

View File

@ -41,13 +41,13 @@ namespace PluralKit.API
public class SystemController : ControllerBase
{
private IDataStore _data;
private IDatabase _conn;
private IDatabase _db;
private IAuthorizationService _auth;
public SystemController(IDataStore data, IDatabase conn, IAuthorizationService auth)
public SystemController(IDataStore data, IDatabase db, IAuthorizationService auth)
{
_data = data;
_conn = conn;
_db = db;
_auth = auth;
}
@ -55,7 +55,7 @@ namespace PluralKit.API
[Authorize]
public async Task<ActionResult<JObject>> GetOwnSystem()
{
var system = await _conn.Execute(c => c.QuerySystem(User.CurrentSystem()));
var system = await _db.Execute(c => c.QuerySystem(User.CurrentSystem()));
return system.ToJson(User.ContextFor(system));
}
@ -94,7 +94,7 @@ namespace PluralKit.API
var auth = await _auth.AuthorizeAsync(User, system, "ViewFrontHistory");
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view front history.");
using (var conn = await _conn.Obtain())
using (var conn = await _db.Obtain())
{
var res = await conn.QueryAsync<SwitchesReturn>(
@"select *, array(
@ -132,17 +132,19 @@ namespace PluralKit.API
[Authorize]
public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes)
{
var system = await _conn.Execute(c => c.QuerySystem(User.CurrentSystem()));
var system = await _db.Execute(c => c.QuerySystem(User.CurrentSystem()));
SystemPatch patch;
try
{
system.ApplyJson(changes);
patch = JsonModelExt.ToSystemPatch(changes);
}
catch (JsonModelParseError e)
{
return BadRequest(e.Message);
}
await _data.SaveSystem(system);
await _db.Execute(conn => conn.UpdateSystem(system.Id, patch));
return Ok(system.ToJson(User.ContextFor(system)));
}
@ -166,7 +168,7 @@ namespace PluralKit.API
// Resolve member objects for all given IDs
IEnumerable<PKMember> membersList;
using (var conn = await _conn.Obtain())
using (var conn = await _db.Obtain())
membersList = (await conn.QueryAsync<PKMember>("select * from members where hid = any(@Hids)", new {Hids = param.Members})).ToList();
foreach (var member in membersList)

View File

@ -34,8 +34,9 @@ namespace PluralKit.Bot
if (ctx.MatchFlag("c", "clear") || ctx.Match("clear"))
{
ctx.System.Name = null;
await _data.SaveSystem(ctx.System);
var clearPatch = new SystemPatch {Name = null};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, clearPatch));
await ctx.Reply($"{Emojis.Success} System name cleared.");
return;
}
@ -50,9 +51,12 @@ namespace PluralKit.Bot
return;
}
if (newSystemName != null && newSystemName.Length > Limits.MaxSystemNameLength) throw Errors.SystemNameTooLongError(newSystemName.Length);
ctx.System.Name = newSystemName;
await _data.SaveSystem(ctx.System);
if (newSystemName != null && newSystemName.Length > Limits.MaxSystemNameLength)
throw Errors.SystemNameTooLongError(newSystemName.Length);
var patch = new SystemPatch {Name = newSystemName};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System name changed.");
}
@ -61,8 +65,9 @@ namespace PluralKit.Bot
if (ctx.MatchFlag("c", "clear") || ctx.Match("clear"))
{
ctx.System.Description = null;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {Description = null};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System description cleared.");
return;
}
@ -84,8 +89,10 @@ namespace PluralKit.Bot
else
{
if (newDescription.Length > Limits.MaxDescriptionLength) throw Errors.DescriptionTooLongError(newDescription.Length);
ctx.System.Description = newDescription;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {Description = newDescription};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System description changed.");
}
}
@ -96,8 +103,9 @@ namespace PluralKit.Bot
if (ctx.MatchFlag("c", "clear") || ctx.Match("clear"))
{
ctx.System.Tag = null;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {Tag = null};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System tag cleared.");
} else if (!ctx.HasNext(skipFlags: false))
{
@ -112,8 +120,10 @@ namespace PluralKit.Bot
if (newTag != null)
if (newTag.Length > Limits.MaxSystemTagLength)
throw Errors.SystemNameTooLongError(newTag.Length);
ctx.System.Tag = newTag;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {Tag = newTag};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System tag changed. Member names will now end with `{newTag}` when proxied.");
}
}
@ -124,8 +134,9 @@ namespace PluralKit.Bot
if (ctx.Match("clear") || ctx.MatchFlag("c", "clear"))
{
ctx.System.AvatarUrl = null;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {AvatarUrl = null};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"{Emojis.Success} System avatar cleared.");
return;
}
@ -149,10 +160,12 @@ namespace PluralKit.Bot
if (member != null)
{
if (member.AvatarHash == null) throw Errors.UserHasNoAvatar;
ctx.System.AvatarUrl = member.GetAvatarUrl(ImageFormat.Png, size: 256);
await _data.SaveSystem(ctx.System);
var embed = new DiscordEmbedBuilder().WithImageUrl(ctx.System.AvatarUrl).Build();
var newUrl = member.GetAvatarUrl(ImageFormat.Png, size: 256);
var patch = new SystemPatch {AvatarUrl = newUrl};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
var embed = new DiscordEmbedBuilder().WithImageUrl(newUrl).Build();
await ctx.Reply(
$"{Emojis.Success} System avatar changed to {member.Username}'s avatar! {Emojis.Warn} Please note that if {member.Username} changes their avatar, the system's avatar will need to be re-set.", embed: embed);
}
@ -163,8 +176,8 @@ namespace PluralKit.Bot
if (url?.Length > Limits.MaxUriLength) throw Errors.InvalidUrl(url);
await ctx.BusyIndicator(() => AvatarUtils.VerifyAvatarOrThrow(url));
ctx.System.AvatarUrl = url;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {AvatarUrl = url};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
var embed = url != null ? new DiscordEmbedBuilder().WithImageUrl(url).Build() : null;
await ctx.Reply($"{Emojis.Success} System avatar changed.", embed: embed);
@ -178,7 +191,8 @@ namespace PluralKit.Bot
if (!await ctx.ConfirmWithReply(ctx.System.Hid))
throw new PKError($"System deletion cancelled. Note that you must reply with your system ID (`{ctx.System.Hid}`) *verbatim*.");
await _data.DeleteSystem(ctx.System);
await _db.Execute(conn => conn.DeleteSystem(ctx.System.Id));
await ctx.Reply($"{Emojis.Success} System deleted.");
}
@ -216,8 +230,9 @@ namespace PluralKit.Bot
if (ctx.MatchFlag("c", "clear") || ctx.Match("clear"))
{
ctx.System.UiTz = "UTC";
await _data.SaveSystem(ctx.System);
var clearPatch = new SystemPatch {UiTz = "UTC"};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, clearPatch));
await ctx.Reply($"{Emojis.Success} System time zone cleared (set to UTC).");
return;
}
@ -237,8 +252,9 @@ namespace PluralKit.Bot
var msg = await ctx.Reply(
$"This will change the system time zone to **{zone.Id}**. The current time is **{currentTime.FormatZoned()}**. Is this correct?");
if (!await ctx.PromptYesNo(msg)) throw Errors.TimezoneChangeCancelled;
ctx.System.UiTz = zone.Id;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {UiTz = zone.Id};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply($"System time zone changed to **{zone.Id}**.");
}
@ -290,39 +306,41 @@ namespace PluralKit.Bot
string levelStr, levelExplanation, subjectStr;
var subjectList = "`description`, `members`, `front`, `fronthistory`, or `all`";
SystemPatch patch = new SystemPatch();
if (ctx.Match("description", "desc", "text", "info"))
{
subjectStr = "description";
ctx.System.DescriptionPrivacy = PopPrivacyLevel("description", out levelStr, out levelExplanation);
patch.DescriptionPrivacy = PopPrivacyLevel("description", out levelStr, out levelExplanation);
}
else if (ctx.Match("members", "memberlist", "list", "mlist"))
{
subjectStr = "member list";
ctx.System.MemberListPrivacy = PopPrivacyLevel("members", out levelStr, out levelExplanation);
patch.MemberListPrivacy = PopPrivacyLevel("members", out levelStr, out levelExplanation);
}
else if (ctx.Match("front", "fronter"))
{
subjectStr = "fronter(s)";
ctx.System.FrontPrivacy = PopPrivacyLevel("front", out levelStr, out levelExplanation);
patch.FrontPrivacy = PopPrivacyLevel("front", out levelStr, out levelExplanation);
}
else if (ctx.Match("switch", "switches", "fronthistory", "fh"))
{
subjectStr = "front history";
ctx.System.FrontHistoryPrivacy = PopPrivacyLevel("fronthistory", out levelStr, out levelExplanation);
patch.FrontHistoryPrivacy = PopPrivacyLevel("fronthistory", out levelStr, out levelExplanation);
}
else if (ctx.Match("all")){
subjectStr = "all";
PrivacyLevel level = PopPrivacyLevel("all", out levelStr, out levelExplanation);
ctx.System.DescriptionPrivacy = level;
ctx.System.MemberListPrivacy = level;
ctx.System.FrontPrivacy = level;
ctx.System.FrontHistoryPrivacy = level;
patch.DescriptionPrivacy = level;
patch.MemberListPrivacy = level;
patch.FrontPrivacy = level;
patch.FrontHistoryPrivacy = level;
}
else
throw new PKSyntaxError($"Invalid privacy subject `{ctx.PopArgument()}` (must be {subjectList}).");
await _data.SaveSystem(ctx.System);
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
if(subjectStr == "all"){
if(levelStr == "private")
await ctx.Reply($"All of your systems privacy settings have been set to **{levelStr}**. Other accounts will now see nothing on the member card.");
@ -345,13 +363,15 @@ namespace PluralKit.Bot
}
else {
if (ctx.Match("on", "enable")) {
ctx.System.PingsEnabled = true;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {PingsEnabled = true};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply("Reaction pings have now been enabled.");
}
if (ctx.Match("off", "disable")) {
ctx.System.PingsEnabled = false;
await _data.SaveSystem(ctx.System);
var patch = new SystemPatch {PingsEnabled = false};
await _db.Execute(conn => conn.UpdateSystem(ctx.System.Id, patch));
await ctx.Reply("Reaction pings have now been disabled.");
}
}

View File

@ -8,10 +8,10 @@ namespace PluralKit.Bot
{
public class Token
{
private IDataStore _data;
public Token(IDataStore data)
private readonly IDatabase _db;
public Token(IDatabase db)
{
_data = data;
_db = db;
}
public async Task GetToken(Context ctx)
@ -35,8 +35,9 @@ namespace PluralKit.Bot
private async Task<string> MakeAndSetNewToken(PKSystem system)
{
system.Token = Core.StringUtils.GenerateToken();
await _data.SaveSystem(system);
var patch = new SystemPatch {Token = StringUtils.GenerateToken()};
await _db.Execute(conn => conn.UpdateSystem(system.Id, patch));
return system.Token;
}

View File

@ -28,16 +28,5 @@ 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});
}
}

View File

@ -10,18 +10,18 @@ namespace PluralKit.Core {
// Additions here should be mirrored in SystemStore::Save
[Key] public SystemId Id { get; }
public string Hid { get; }
public string Name { get; set; }
public string Description { get; set; }
public string Tag { get; set; }
public string AvatarUrl { get; set; }
public string Token { get; set; }
public string Name { get; }
public string Description { get; }
public string Tag { get; }
public string AvatarUrl { get; }
public string Token { get; }
public Instant Created { get; }
public string UiTz { get; set; }
public bool PingsEnabled { get; set; }
public PrivacyLevel DescriptionPrivacy { get; set; }
public PrivacyLevel MemberListPrivacy { get; set; }
public PrivacyLevel FrontPrivacy { get; set; }
public PrivacyLevel FrontHistoryPrivacy { get; set; }
public bool PingsEnabled { get; }
public PrivacyLevel DescriptionPrivacy { get; }
public PrivacyLevel MemberListPrivacy { get;}
public PrivacyLevel FrontPrivacy { get; }
public PrivacyLevel FrontHistoryPrivacy { get; }
[JsonIgnore] public DateTimeZone Zone => DateTimeZoneProviders.Tzdb.GetZoneOrNull(UiTz);
}

View File

@ -4,7 +4,7 @@ using NodaTime;
namespace PluralKit.Core
{
public class MemberPatch: PatchObject<MemberId, PKMember>
public class MemberPatch: PatchObject
{
public Partial<string> Name { get; set; }
public Partial<string?> DisplayName { get; set; }

View File

@ -0,0 +1,31 @@
using System.Threading.Tasks;
using Dapper;
namespace PluralKit.Core
{
public static class ModelPatchExt
{
public static Task<PKSystem> UpdateSystem(this IPKConnection conn, SystemId id, SystemPatch patch)
{
var (query, pms) = patch.Apply(new UpdateQueryBuilder("systems", "id = @id"))
.WithConstant("id", id)
.Build("returning *");
return conn.QueryFirstAsync<PKSystem>(query, pms);
}
public static Task DeleteSystem(this IPKConnection conn, SystemId id) =>
conn.ExecuteAsync("delete from systems where id = @Id", new {Id = id});
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});
}
}

View File

@ -1,8 +1,6 @@
using PluralKit.Core;
namespace PluralKit.Core
namespace PluralKit.Core
{
public abstract class PatchObject<TKey, TObj>
public abstract class PatchObject
{
public abstract UpdateQueryBuilder Apply(UpdateQueryBuilder b);
}

View File

@ -0,0 +1,31 @@
#nullable enable
namespace PluralKit.Core
{
public class SystemPatch: PatchObject
{
public Partial<string?> Name { get; set; }
public Partial<string?> Description { get; set; }
public Partial<string?> Tag { get; set; }
public Partial<string?> AvatarUrl { get; set; }
public Partial<string?> Token { get; set; }
public Partial<string> UiTz { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
public Partial<PrivacyLevel> MemberListPrivacy { get; set; }
public Partial<PrivacyLevel> FrontPrivacy { get; set; }
public Partial<PrivacyLevel> FrontHistoryPrivacy { get; set; }
public Partial<bool> PingsEnabled { get; set; }
public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b
.With("name", Name)
.With("description", Description)
.With("tag", Tag)
.With("avatar_url", AvatarUrl)
.With("token", Token)
.With("ui_tz", UiTz)
.With("description_privacy", DescriptionPrivacy)
.With("member_list_privacy", MemberListPrivacy)
.With("front_privacy", FrontPrivacy)
.With("front_history_privacy", FrontHistoryPrivacy)
.With("pings_enabled", PingsEnabled);
}
}

View File

@ -116,16 +116,17 @@ namespace PluralKit.Core
await _data.AddAccount(system, accountId);
}
await using var conn = await _db.Obtain();
// Apply system info
system.Name = data.Name;
if (data.Description != null) system.Description = data.Description;
if (data.Tag != null) system.Tag = data.Tag;
if (data.AvatarUrl != null) system.AvatarUrl = data.AvatarUrl;
if (data.TimeZone != null) system.UiTz = data.TimeZone ?? "UTC";
await _data.SaveSystem(system);
var patch = new SystemPatch {Name = data.Name};
if (data.Description != null) patch.Description = data.Description;
if (data.Tag != null) patch.Tag = data.Tag;
if (data.AvatarUrl != null) patch.AvatarUrl = data.AvatarUrl;
if (data.TimeZone != null) patch.UiTz = data.TimeZone ?? "UTC";
await conn.UpdateSystem(system.Id, patch);
// -- Member/switch import --
await using var conn = await _db.Obtain();
await using (var imp = await BulkImporter.Begin(system, conn))
{
// Tally up the members that didn't exist before, and check member count on import

View File

@ -103,20 +103,6 @@ namespace PluralKit.Core {
/// <exception>Throws an exception (TODO: which?) if the given account is not linked to the given system.</exception>
Task RemoveAccount(PKSystem system, ulong accountToRemove);
/// <summary>
/// Saves the information within the given <see cref="PKSystem"/> struct to the data store.
/// </summary>
Task SaveSystem(PKSystem system);
/// <summary>
/// Deletes the given system from the database.
/// </summary>
/// <para>
/// This will also delete all the system's members, all system switches, and every message that has been proxied
/// by members in the system.
/// </para>
Task DeleteSystem(PKSystem system);
/// <summary>
/// Gets a member by its user-facing human ID.
/// </summary>