diff --git a/PluralKit.API/Controllers/v2/GuildControllerV2.cs b/PluralKit.API/Controllers/v2/GuildControllerV2.cs index ed0631cb..e3abd0de 100644 --- a/PluralKit.API/Controllers/v2/GuildControllerV2.cs +++ b/PluralKit.API/Controllers/v2/GuildControllerV2.cs @@ -17,40 +17,119 @@ namespace PluralKit.API public GuildControllerV2(IServiceProvider svc) : base(svc) { } - [HttpGet("systems/{system}/guilds/{guild_id}")] - public async Task SystemGuildGet(string system, ulong guild_id) + [HttpGet("systems/@me/guilds/{guild_id}")] + public async Task SystemGuildGet(ulong guild_id) { - return new ObjectResult("Unimplemented") - { - StatusCode = 501 - }; + var system = await ResolveSystem("@me"); + var settings = await _repo.GetSystemGuild(guild_id, system.Id, defaultInsert: false); + if (settings == null) + throw APIErrors.SystemGuildNotFound; + + PKMember member = null; + if (settings.AutoproxyMember != null) + member = await _repo.GetMember(settings.AutoproxyMember.Value); + + return Ok(settings.ToJson(member?.Hid)); } - [HttpPatch("systems/{system}/guilds/{guild_id}")] - public async Task SystemGuildPatch(string system, ulong guild_id, [FromBody] JObject data) + [HttpPatch("systems/@me/guilds/{guild_id}")] + public async Task DoSystemGuildPatch(ulong guild_id, [FromBody] JObject data) { - return new ObjectResult("Unimplemented") + var system = await ResolveSystem("@me"); + var settings = await _repo.GetSystemGuild(guild_id, system.Id, defaultInsert: false); + if (settings == null) + throw APIErrors.SystemGuildNotFound; + + MemberId? memberId = null; + if (data.ContainsKey("autoproxy_member")) { - StatusCode = 501 - }; + if (data["autoproxy_member"].Type != JTokenType.Null) + { + var member = await ResolveMember(data.Value("autoproxy_member")); + if (member == null) + throw APIErrors.MemberNotFound; + + memberId = member.Id; + } + } + else + memberId = settings.AutoproxyMember; + + SystemGuildPatch patch = null; + try + { + patch = SystemGuildPatch.FromJson(data, memberId); + patch.AssertIsValid(); + } + catch (ValidationError e) + { + // todo + return BadRequest(e.Message); + } + + // this is less than great, but at least it's legible + if (patch.AutoproxyMember.Value == null) + if (patch.AutoproxyMode.IsPresent) + { + if (patch.AutoproxyMode.Value == AutoproxyMode.Member) + throw APIErrors.MissingAutoproxyMember; + } + else if (settings.AutoproxyMode == AutoproxyMode.Member) + throw APIErrors.MissingAutoproxyMember; + + var newSettings = await _repo.UpdateSystemGuild(system.Id, guild_id, patch); + + PKMember? newMember = null; + if (newSettings.AutoproxyMember != null) + newMember = await _repo.GetMember(newSettings.AutoproxyMember.Value); + return Ok(newSettings.ToJson(newMember?.Hid)); } - [HttpGet("members/{member}/guilds/{guild_id}")] - public async Task MemberGuildGet(string member, ulong guild_id) + [HttpGet("members/{memberRef}/guilds/{guild_id}")] + public async Task MemberGuildGet(string memberRef, ulong guild_id) { - return new ObjectResult("Unimplemented") - { - StatusCode = 501 - }; + var system = await ResolveSystem("@me"); + var member = await ResolveMember(memberRef); + if (member == null) + throw APIErrors.MemberNotFound; + if (member.System != system.Id) + throw APIErrors.NotOwnMemberError; + + var settings = await _repo.GetMemberGuild(guild_id, member.Id, defaultInsert: false); + if (settings == null) + throw APIErrors.MemberGuildNotFound; + + return Ok(settings.ToJson()); } - [HttpPatch("members/{member}/guilds/{guild_id}")] - public async Task MemberGuildPatch(string member, ulong guild_id, [FromBody] JObject data) + [HttpPatch("members/{memberRef}/guilds/{guild_id}")] + public async Task DoMemberGuildPatch(string memberRef, ulong guild_id, [FromBody] JObject data) { - return new ObjectResult("Unimplemented") + var system = await ResolveSystem("@me"); + var member = await ResolveMember(memberRef); + if (member == null) + throw APIErrors.MemberNotFound; + if (member.System != system.Id) + throw APIErrors.NotOwnMemberError; + + var settings = await _repo.GetMemberGuild(guild_id, member.Id, defaultInsert: false); + if (settings == null) + throw APIErrors.MemberGuildNotFound; + + MemberGuildPatch patch = null; + try { - StatusCode = 501 - }; + patch = MemberGuildPatch.FromJson(data); + patch.AssertIsValid(); + } + catch (ValidationError e) + { + // todo + return BadRequest(e.Message); + } + + var newSettings = await _repo.UpdateMemberGuild(member.Id, guild_id, patch); + return Ok(newSettings.ToJson()); } diff --git a/PluralKit.API/Errors.cs b/PluralKit.API/Errors.cs index 18d43edc..01b2cd85 100644 --- a/PluralKit.API/Errors.cs +++ b/PluralKit.API/Errors.cs @@ -25,7 +25,7 @@ namespace PluralKit.API public class ModelParseError: PKError { - public ModelParseError() : base(400, 0, "Error parsing JSON model") + public ModelParseError() : base(400, 40001, "Error parsing JSON model") { // todo } @@ -47,16 +47,19 @@ namespace PluralKit.API public static PKError GroupNotFound = new(404, 20003, "Group not found."); public static PKError MessageNotFound = new(404, 20004, "Message not found."); public static PKError SwitchNotFound = new(404, 20005, "Switch not found, switch is associated to different system, or unauthorized to view front history."); + public static PKError SystemGuildNotFound = new(404, 20006, "No system guild settings found for target guild."); + public static PKError MemberGuildNotFound = new(404, 20007, "No member guild settings found for target guild."); public static PKError UnauthorizedMemberList = new(403, 30001, "Unauthorized to view member list"); public static PKError UnauthorizedGroupList = new(403, 30002, "Unauthorized to view group list"); public static PKError UnauthorizedGroupMemberList = new(403, 30003, "Unauthorized to view group member list"); public static PKError UnauthorizedCurrentFronters = new(403, 30004, "Unauthorized to view current fronters."); public static PKError UnauthorizedFrontHistory = new(403, 30005, "Unauthorized to view front history."); - public static PKError NotOwnMemberError = new(403, 40001, "Target member is not part of your system."); - public static PKError NotOwnGroupError = new(403, 40002, "Target group is not part of your system."); + public static PKError NotOwnMemberError = new(403, 30006, "Target member is not part of your system."); + public static PKError NotOwnGroupError = new(403, 30006, "Target group is not part of your system."); // todo: somehow add the memberRef to the JSON - public static PKError NotOwnMemberErrorWithRef(string memberRef) => new(403, 40003, $"Member '{memberRef}' is not part of your system."); - public static PKError NotOwnGroupErrorWithRef(string groupRef) => new(403, 40004, $"Group '{groupRef}' is not part of your system."); + public static PKError NotOwnMemberErrorWithRef(string memberRef) => new(403, 30008, $"Member '{memberRef}' is not part of your system."); + public static PKError NotOwnGroupErrorWithRef(string groupRef) => new(403, 30009, $"Group '{groupRef}' is not part of your system."); + public static PKError MissingAutoproxyMember = new(400, 40002, "Missing autoproxy member for member-mode autoproxy."); public static PKError Unimplemented = new(501, 50001, "Unimplemented"); } } \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs index 5eb8ed26..851c83d2 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Guild.cs @@ -21,8 +21,14 @@ namespace PluralKit.Core } - public Task GetSystemGuild(ulong guild, SystemId system) + public Task GetSystemGuild(ulong guild, SystemId system, bool defaultInsert = true) { + if (!defaultInsert) + return _db.QueryFirst(new Query("system_guild") + .Where("guild", guild) + .Where("system", system) + ); + var query = new Query("system_guild").AsInsert(new { guild = guild, @@ -33,16 +39,22 @@ namespace PluralKit.Core ); } - public Task UpdateSystemGuild(SystemId system, ulong guild, SystemGuildPatch patch) + public Task UpdateSystemGuild(SystemId system, ulong guild, SystemGuildPatch patch) { _logger.Information("Updated {SystemId} in guild {GuildId}: {@SystemGuildPatch}", system, guild, patch); var query = patch.Apply(new Query("system_guild").Where("system", system).Where("guild", guild)); - return _db.ExecuteQuery(query, extraSql: "returning *"); + return _db.QueryFirst(query, extraSql: "returning *"); } - public Task GetMemberGuild(ulong guild, MemberId member) + public Task GetMemberGuild(ulong guild, MemberId member, bool defaultInsert = true) { + if (!defaultInsert) + return _db.QueryFirst(new Query("member_guild") + .Where("guild", guild) + .Where("member", member) + ); + var query = new Query("member_guild").AsInsert(new { guild = guild, @@ -53,11 +65,11 @@ namespace PluralKit.Core ); } - public Task UpdateMemberGuild(MemberId member, ulong guild, MemberGuildPatch patch) + public Task UpdateMemberGuild(MemberId member, ulong guild, MemberGuildPatch patch) { _logger.Information("Updated {MemberId} in guild {GuildId}: {@MemberGuildPatch}", member, guild, patch); var query = patch.Apply(new Query("member_guild").Where("member", member).Where("guild", guild)); - return _db.ExecuteQuery(query, extraSql: "returning *"); + return _db.QueryFirst(query, extraSql: "returning *"); } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/MemberGuildSettings.cs b/PluralKit.Core/Models/MemberGuildSettings.cs index 3347275e..db3dfba4 100644 --- a/PluralKit.Core/Models/MemberGuildSettings.cs +++ b/PluralKit.Core/Models/MemberGuildSettings.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json.Linq; + #nullable enable namespace PluralKit.Core { @@ -8,4 +10,17 @@ namespace PluralKit.Core public string? DisplayName { get; } public string? AvatarUrl { get; } } + + public static class MemberGuildExt + { + public static JObject ToJson(this MemberGuildSettings settings) + { + var o = new JObject(); + + o.Add("display_name", settings.DisplayName); + o.Add("avatar_url", settings.AvatarUrl); + + return o; + } + } } \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/MemberGuildPatch.cs b/PluralKit.Core/Models/Patch/MemberGuildPatch.cs index 5207fb7e..7a51df25 100644 --- a/PluralKit.Core/Models/Patch/MemberGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberGuildPatch.cs @@ -1,5 +1,7 @@ #nullable enable +using Newtonsoft.Json.Linq; + using SqlKata; namespace PluralKit.Core @@ -13,5 +15,28 @@ namespace PluralKit.Core .With("display_name", DisplayName) .With("avatar_url", AvatarUrl) ); + + public new void AssertIsValid() + { + if (DisplayName.Value != null) + AssertValid(DisplayName.Value, "display_name", Limits.MaxMemberNameLength); + if (AvatarUrl.Value != null) + AssertValid(AvatarUrl.Value, "avatar_url", Limits.MaxUriLength, + s => MiscUtils.TryMatchUri(s, out var avatarUri)); + } + +#nullable disable + public static MemberGuildPatch FromJson(JObject o) + { + var patch = new MemberGuildPatch(); + + if (o.ContainsKey("display_name")) + patch.DisplayName = o.Value("display_name").NullIfEmpty(); + + if (o.ContainsKey("avatar_url")) + patch.AvatarUrl = o.Value("avatar_url").NullIfEmpty(); + + return patch; + } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs index a5642f85..241b12d5 100644 --- a/PluralKit.Core/Models/Patch/SystemGuildPatch.cs +++ b/PluralKit.Core/Models/Patch/SystemGuildPatch.cs @@ -1,5 +1,7 @@ #nullable enable +using Newtonsoft.Json.Linq; + using SqlKata; namespace PluralKit.Core @@ -19,5 +21,33 @@ namespace PluralKit.Core .With("tag", Tag) .With("tag_enabled", TagEnabled) ); + + public new void AssertIsValid() + { + if (Tag.Value != null) + AssertValid(Tag.Value, "tag", Limits.MaxSystemTagLength); + } + +#nullable disable + public static SystemGuildPatch FromJson(JObject o, MemberId? memberId) + { + var patch = new SystemGuildPatch(); + + if (o.ContainsKey("proxying_enabled") && o["proxying_enabled"].Type != JTokenType.Null) + patch.ProxyEnabled = o.Value("proxying_enabled"); + + if (o.ContainsKey("autoproxy_mode") && o["autoproxy_mode"].ParseAutoproxyMode() is { } autoproxyMode) + patch.AutoproxyMode = autoproxyMode; + + patch.AutoproxyMember = memberId; + + if (o.ContainsKey("tag")) + patch.Tag = o.Value("tag").NullIfEmpty(); + + if (o.ContainsKey("tag_enabled") && o["tag_enabled"].Type != JTokenType.Null) + patch.TagEnabled = o.Value("tag_enabled"); + + return patch; + } } } \ No newline at end of file diff --git a/PluralKit.Core/Models/SystemGuildSettings.cs b/PluralKit.Core/Models/SystemGuildSettings.cs index 83b6a2cd..1d47d15e 100644 --- a/PluralKit.Core/Models/SystemGuildSettings.cs +++ b/PluralKit.Core/Models/SystemGuildSettings.cs @@ -1,5 +1,10 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Converters; + namespace PluralKit.Core { + [JsonConverter(typeof(StringEnumConverter))] public enum AutoproxyMode { Off = 1, @@ -20,4 +25,44 @@ namespace PluralKit.Core public string? Tag { get; } public bool TagEnabled { get; } } + + public static class SystemGuildExt + { + public static JObject ToJson(this SystemGuildSettings settings, string? memberHid = null) + { + var o = new JObject(); + + o.Add("proxying_enabled", settings.ProxyEnabled); + o.Add("autoproxy_mode", settings.AutoproxyMode.ToString().ToLower()); + o.Add("autoproxy_member", memberHid); + o.Add("tag", settings.Tag); + o.Add("tag_enabled", settings.TagEnabled); + + return o; + } + + public static AutoproxyMode? ParseAutoproxyMode(this JToken o) + { + if (o.Type == JTokenType.Null) + return AutoproxyMode.Off; + else if (o.Type != JTokenType.String) + return null; + + var value = o.Value(); + + switch (value) + { + case "off": + return AutoproxyMode.Off; + case "front": + return AutoproxyMode.Front; + case "latch": + return AutoproxyMode.Latch; + case "member": + return AutoproxyMode.Member; + default: + throw new ValidationError($"Value '{value}' is not a valid autoproxy mode."); + } + } + } } \ No newline at end of file