diff --git a/PluralKit.API/Controllers/v2/AutoproxyControllerV2.cs b/PluralKit.API/Controllers/v2/AutoproxyControllerV2.cs new file mode 100644 index 00000000..23a118be --- /dev/null +++ b/PluralKit.API/Controllers/v2/AutoproxyControllerV2.cs @@ -0,0 +1,77 @@ +using Microsoft.AspNetCore.Mvc; + +using Newtonsoft.Json.Linq; + +using PluralKit.Core; + +namespace PluralKit.API; + +[ApiController] +[Route("v2")] +public class AutoproxyControllerV2: PKControllerBase +{ + public AutoproxyControllerV2(IServiceProvider svc) : base(svc) { } + + // asp.net why + + [HttpGet("systems/{systemRef}/autoproxy")] + public Task GetWrapper([FromRoute] string systemRef, [FromQuery] ulong? guild_id, [FromQuery] ulong? channel_id) + => Entrypoint(systemRef, guild_id, channel_id, null); + + [HttpPatch("systems/{systemRef}/autoproxy")] + public Task PatchWrapper([FromRoute] string systemRef, [FromQuery] ulong? guild_id, [FromQuery] ulong? channel_id, [FromBody] JObject? data) + => Entrypoint(systemRef, guild_id, channel_id, data); + + public async Task Entrypoint(string systemRef, ulong? guild_id, ulong? channel_id, JObject? data) + { + var system = await ResolveSystem(systemRef); + if (ContextFor(system) != LookupContext.ByOwner) + throw Errors.GenericMissingPermissions; + + if (guild_id == null || channel_id != null) + throw Errors.Unimplemented; + + var settings = await _repo.GetAutoproxySettings(system.Id, guild_id, channel_id); + if (settings == null) + return NotFound(); + + if (HttpContext.Request.Method == "GET") + return await Get(settings); + else if (HttpContext.Request.Method == "PATCH") + return await Patch(system, guild_id, channel_id, data, settings); + else return StatusCode(415); + } + + private async Task Get(AutoproxySettings settings) + { + string hid = null; + if (settings.AutoproxyMember != null) + hid = (await _repo.GetMember(settings.AutoproxyMember.Value))?.Hid; + + return Ok(settings.ToJson(hid)); + } + + private async Task Patch(PKSystem system, ulong? guildId, ulong? channelId, JObject data, AutoproxySettings oldData) + { + var updateMember = data.ContainsKey("autoproxy_member"); + + PKMember? member = null; + if (updateMember) + member = await ResolveMember(data.Value("autoproxy_member")); + + var patch = AutoproxyPatch.FromJson(data, member?.Id); + + patch.AssertIsValid(); + if (updateMember && member == null) + patch.Errors.Add(new("autoproxy_member", "Member not found.")); + if (updateMember && ((patch.AutoproxyMode.IsPresent && patch.AutoproxyMode.Value == AutoproxyMode.Latch) || oldData.AutoproxyMode == AutoproxyMode.Latch)) + patch.Errors.Add(new("autoproxy_member", "Cannot update autoproxy member if autoproxy mode is set to latch")); + if (patch.Errors.Count > 0) + throw new ModelParseError(patch.Errors); + + var res = await _repo.UpdateAutoproxy(system.Id, guildId, channelId, patch); + if (!updateMember && oldData.AutoproxyMember != null) + member = await _repo.GetMember(oldData.AutoproxyMember.Value); + return Ok(res.ToJson(member?.Hid)); + } +} \ No newline at end of file diff --git a/PluralKit.Core/Database/Repository/ModelRepository.Autoproxy.cs b/PluralKit.Core/Database/Repository/ModelRepository.Autoproxy.cs index c0ad0f3a..5e153176 100644 --- a/PluralKit.Core/Database/Repository/ModelRepository.Autoproxy.cs +++ b/PluralKit.Core/Database/Repository/ModelRepository.Autoproxy.cs @@ -6,7 +6,7 @@ namespace PluralKit.Core; public partial class ModelRepository { - public async Task UpdateAutoproxy(SystemId system, ulong? guildId, ulong? channelId, AutoproxyPatch patch) + public Task UpdateAutoproxy(SystemId system, ulong? guildId, ulong? channelId, AutoproxyPatch patch) { var locationStr = guildId != null ? "guild" : (channelId != null ? "channel" : "global"); _logger.Information("Updated autoproxy for {SystemId} in location {location}: {@AutoproxyPatch}", system, locationStr, patch); @@ -17,7 +17,7 @@ public partial class ModelRepository .Where("channel_id", channelId ?? 0) ); _ = _dispatch.Dispatch(system, guildId, channelId, patch); - await _db.ExecuteQuery(query); + return _db.QueryFirst(query, "returning *"); } // todo: this might break with differently scoped autoproxy diff --git a/PluralKit.Core/Models/Autoproxy.cs b/PluralKit.Core/Models/Autoproxy.cs index a8df8078..53756837 100644 --- a/PluralKit.Core/Models/Autoproxy.cs +++ b/PluralKit.Core/Models/Autoproxy.cs @@ -16,7 +16,7 @@ public class AutoproxySettings { public AutoproxyMode AutoproxyMode { get; } public MemberId? AutoproxyMember { get; } - public Instant LastLatchTimestamp { get; } + public Instant? LastLatchTimestamp { get; } } public static class AutoproxyExt @@ -27,7 +27,8 @@ public static class AutoproxyExt // tbd o.Add("autoproxy_mode", settings.AutoproxyMode.ToString().ToLower()); - o.Add("autoproxy_member", memberHid); + o.Add("autoproxy_member", settings.AutoproxyMode == AutoproxyMode.Front ? null : memberHid); + o.Add("last_latch_timestamp", settings.LastLatchTimestamp?.FormatExport()); return o; } diff --git a/PluralKit.Core/Models/Patch/AutoproxyPatch.cs b/PluralKit.Core/Models/Patch/AutoproxyPatch.cs index 0f42bc5a..66d3c35c 100644 --- a/PluralKit.Core/Models/Patch/AutoproxyPatch.cs +++ b/PluralKit.Core/Models/Patch/AutoproxyPatch.cs @@ -1,3 +1,5 @@ +using Newtonsoft.Json.Linq; + using NodaTime; using SqlKata; @@ -16,4 +18,30 @@ public class AutoproxyPatch: PatchObject .With("autoproxy_member", AutoproxyMember) .With("last_latch_timestamp", LastLatchTimestamp) ); + + public new void AssertIsValid() + { + // this is checked in FromJson + // not really the best way to do this, maybe fix at some point? + if ((int?)AutoproxyMode.Value == -1) + Errors.Add(new("autoproxy_mode")); + } + + public static AutoproxyPatch FromJson(JObject o, MemberId? autoproxyMember = null) + { + var p = new AutoproxyPatch(); + + if (o.ContainsKey("autoproxy_mode")) + { + var (autoproxyMode, error) = o.Value("autoproxy_mode").ParseAutoproxyMode(); + if (error != null) + p.AutoproxyMode = Partial.Present((AutoproxyMode)(-1)); + else + p.AutoproxyMode = autoproxyMode.Value; + } + + p.AutoproxyMember = autoproxyMember ?? Partial.Absent; + + return p; + } } \ No newline at end of file diff --git a/docs/content/api/endpoints.md b/docs/content/api/endpoints.md index e2880b70..551dfbfb 100644 --- a/docs/content/api/endpoints.md +++ b/docs/content/api/endpoints.md @@ -60,6 +60,40 @@ Takes a partial [system guild settings](/api/models#system-guild-settings-model) Returns a [system guild settings](/api/models#system-guild-settings-model) object on success. +### Get System Autoproxy Settings + +GET `/systems/@me/autoproxy` + +Query String Parameters +|name|type| +|---|---| +|guild_id?|snowflake| +|channel_id?|snowflake| + +Returns an [autoproxy settings](/api/models/#autoproxy-settings-model) object on success. + +::: warning +Currently, only autoproxy with `guild_id` is supported. The API will return an error message if you specify `channel_id`, or do not specify a `guild_id`. +::: + +### Update System Autoproxy Settings + +PATCH `/systems/@me/autoproxy` + +Query String Parameters +|name|type| +|---|---| +|guild_id?|snowflake| +|channel_id?|snowflake| + +Takes a partial [autoproxy settings](/api/models/#autoproxy-settings-model) object. + +Returns an [autoproxy settings](/api/models/#autoproxy-settings-model) object on success. + +::: warning +Currently, only autoproxy with `guild_id` is supported. The API will return an error message if you specify `channel_id`, or do not specify a `guild_id`. +::: + --- ## Members diff --git a/docs/content/api/models.md b/docs/content/api/models.md index e9e31df7..f7e71523 100644 --- a/docs/content/api/models.md +++ b/docs/content/api/models.md @@ -124,7 +124,14 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID. |tag|?string|79-character limit| |tag_enabled|boolean|| - ### Member guild settings model