feat(apiv2): switch endpoints
This commit is contained in:
parent
f602f22a3d
commit
5add31c77e
@ -1,7 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
@ -32,4 +36,19 @@ namespace PluralKit.API
|
||||
return o;
|
||||
}
|
||||
}
|
||||
|
||||
public struct FrontersReturnNew
|
||||
{
|
||||
[JsonProperty("id")] public Guid Uuid { get; set; }
|
||||
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
|
||||
[JsonProperty("members")] public IEnumerable<JObject> Members { get; set; }
|
||||
}
|
||||
|
||||
public struct SwitchesReturnNew
|
||||
{
|
||||
[JsonProperty("id")] public Guid Uuid { get; set; }
|
||||
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
|
||||
[JsonProperty("members")] public IEnumerable<string> Members { get; set; }
|
||||
}
|
||||
|
||||
}
|
@ -32,6 +32,7 @@ namespace PluralKit.API
|
||||
|
||||
public struct PostSwitchParams
|
||||
{
|
||||
public Instant? Timestamp { get; set; }
|
||||
public ICollection<string> Members { get; set; }
|
||||
}
|
||||
|
||||
|
@ -16,13 +16,6 @@ using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public struct SwitchesReturnNew
|
||||
{
|
||||
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
|
||||
[JsonProperty("id")] public Guid Uuid { get; set; }
|
||||
[JsonProperty("members")] public IEnumerable<string> Members { get; set; }
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("2.0")]
|
||||
[Route("v{version:apiVersion}")]
|
||||
@ -77,21 +70,60 @@ namespace PluralKit.API
|
||||
return NoContent();
|
||||
|
||||
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
||||
return Ok(new FrontersReturn
|
||||
return Ok(new FrontersReturnNew
|
||||
{
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
|
||||
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync(),
|
||||
Uuid = sw.Uuid,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[HttpPost("systems/{system}/switches")]
|
||||
public async Task<IActionResult> SwitchCreate(string system, [FromBody] JObject data)
|
||||
[HttpPost("systems/@me/switches")]
|
||||
public async Task<IActionResult> SwitchCreate([FromBody] PostSwitchParams data)
|
||||
{
|
||||
return new ObjectResult("Unimplemented")
|
||||
if (data.Members.Distinct().Count() != data.Members.Count)
|
||||
throw APIErrors.DuplicateMembersInList;
|
||||
|
||||
var system = await ResolveSystem("@me");
|
||||
|
||||
if (data.Timestamp != null && await _repo.GetSwitches(system.Id).Select(x => x.Timestamp).ContainsAsync(data.Timestamp.Value))
|
||||
throw APIErrors.SameSwitchTimestampError;
|
||||
|
||||
var members = new List<PKMember>();
|
||||
|
||||
foreach (var memberRef in data.Members)
|
||||
{
|
||||
StatusCode = 501
|
||||
};
|
||||
var member = await ResolveMember(memberRef);
|
||||
if (member == null)
|
||||
// todo: which member
|
||||
throw APIErrors.MemberNotFound;
|
||||
if (member.System != system.Id)
|
||||
throw APIErrors.NotOwnMemberErrorWithRef(memberRef);
|
||||
members.Add(member);
|
||||
}
|
||||
|
||||
// We get the current switch, if it exists
|
||||
var latestSwitch = await _repo.GetLatestSwitch(system.Id);
|
||||
if (latestSwitch != null && (data.Timestamp == null || data.Timestamp > latestSwitch.Timestamp))
|
||||
{
|
||||
var latestSwitchMembers = _db.Execute(conn => _repo.GetSwitchMembers(conn, latestSwitch.Id));
|
||||
|
||||
// Bail if this switch is identical to the latest one
|
||||
if (await latestSwitchMembers.Select(m => m.Hid).SequenceEqualAsync(members.Select(m => m.Hid).ToAsyncEnumerable()))
|
||||
throw APIErrors.SameSwitchMembersError;
|
||||
}
|
||||
|
||||
var newSwitch = await _db.Execute(conn => _repo.AddSwitch(conn, system.Id, members.Select(m => m.Id).ToList()));
|
||||
if (data.Timestamp != null)
|
||||
await _repo.MoveSwitch(newSwitch.Id, data.Timestamp.Value);
|
||||
|
||||
return Ok(new FrontersReturnNew
|
||||
{
|
||||
Uuid = newSwitch.Uuid,
|
||||
Timestamp = data.Timestamp != null ? data.Timestamp.Value : newSwitch.Timestamp,
|
||||
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2)),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -99,7 +131,7 @@ namespace PluralKit.API
|
||||
public async Task<IActionResult> SwitchGet(string systemRef, string switchRef)
|
||||
{
|
||||
if (!Guid.TryParse(switchRef, out var switchId))
|
||||
throw APIErrors.SwitchNotFound;
|
||||
throw APIErrors.InvalidSwitchId;
|
||||
|
||||
var system = await ResolveSystem(systemRef);
|
||||
if (system == null)
|
||||
@ -107,45 +139,115 @@ namespace PluralKit.API
|
||||
|
||||
var sw = await _repo.GetSwitchByUuid(switchId);
|
||||
if (sw == null || system.Id != sw.System)
|
||||
throw APIErrors.SwitchNotFound;
|
||||
throw APIErrors.SwitchNotFoundPublic;
|
||||
|
||||
var ctx = this.ContextFor(system);
|
||||
|
||||
if (!system.FrontHistoryPrivacy.CanAccess(ctx))
|
||||
throw APIErrors.SwitchNotFound;
|
||||
throw APIErrors.SwitchNotFoundPublic;
|
||||
|
||||
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
||||
return Ok(new FrontersReturn
|
||||
return Ok(new FrontersReturnNew
|
||||
{
|
||||
Uuid = sw.Uuid,
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPatch("systems/{system}/switches/{switch_id}")]
|
||||
public async Task<IActionResult> SwitchPatch(string system, [FromBody] JObject data)
|
||||
[HttpPatch("systems/@me/switches/{switchRef}")]
|
||||
public async Task<IActionResult> SwitchPatch(string switchRef, [FromBody] JObject data)
|
||||
{
|
||||
return new ObjectResult("Unimplemented")
|
||||
// for now, don't need to make a PatchObject for this, since it's only one param
|
||||
|
||||
if (!Guid.TryParse(switchRef, out var switchId))
|
||||
throw APIErrors.InvalidSwitchId;
|
||||
|
||||
var value = data.Value<Instant>("timestamp");
|
||||
if (value == null)
|
||||
// todo
|
||||
throw APIErrors.GenericBadRequest;
|
||||
|
||||
var system = await ResolveSystem("@me");
|
||||
if (system == null)
|
||||
throw APIErrors.SystemNotFound;
|
||||
|
||||
var sw = await _repo.GetSwitchByUuid(switchId);
|
||||
if (sw == null || system.Id != sw.System)
|
||||
throw APIErrors.SwitchNotFoundPublic;
|
||||
|
||||
if (await _repo.GetSwitches(system.Id).Select(x => x.Timestamp).ContainsAsync(value))
|
||||
throw APIErrors.SameSwitchTimestampError;
|
||||
|
||||
await _repo.MoveSwitch(sw.Id, value);
|
||||
|
||||
var members = await _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id)).ToListAsync();
|
||||
return Ok(new FrontersReturnNew
|
||||
{
|
||||
StatusCode = 501
|
||||
};
|
||||
Uuid = sw.Uuid,
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2)),
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPatch("systems/@me/switches/{switchRef}/members")]
|
||||
public async Task<IActionResult> SwitchMemberPatch(string switchRef, [FromBody] JArray data)
|
||||
{
|
||||
if (!Guid.TryParse(switchRef, out var switchId))
|
||||
|
||||
if (data.Distinct().Count() != data.Count)
|
||||
throw APIErrors.DuplicateMembersInList;
|
||||
|
||||
var system = await ResolveSystem("@me");
|
||||
|
||||
var sw = await _repo.GetSwitchByUuid(switchId);
|
||||
if (sw == null)
|
||||
throw APIErrors.SwitchNotFound;
|
||||
|
||||
var members = new List<PKMember>();
|
||||
|
||||
foreach (var JmemberRef in data)
|
||||
{
|
||||
var memberRef = JmemberRef.Value<string>();
|
||||
|
||||
var member = await ResolveMember(memberRef);
|
||||
if (member == null)
|
||||
// todo: which member
|
||||
throw APIErrors.MemberNotFound;
|
||||
if (member.System != system.Id)
|
||||
throw APIErrors.NotOwnMemberErrorWithRef(memberRef);
|
||||
|
||||
members.Add(member);
|
||||
}
|
||||
|
||||
var latestSwitchMembers = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
||||
|
||||
if (await latestSwitchMembers.Select(m => m.Hid).SequenceEqualAsync(members.Select(m => m.Hid).ToAsyncEnumerable()))
|
||||
throw APIErrors.SameSwitchMembersError;
|
||||
|
||||
await _db.Execute(conn => _repo.EditSwitch(conn, sw.Id, members.Select(x => x.Id).ToList()));
|
||||
return Ok(new FrontersReturnNew
|
||||
{
|
||||
Uuid = sw.Uuid,
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2)),
|
||||
});
|
||||
}
|
||||
|
||||
[HttpDelete("systems/@me/switches/{switchRef}")]
|
||||
public async Task<IActionResult> SwitchDelete(string switchRef)
|
||||
{
|
||||
if (!Guid.TryParse(switchRef, out var switchId))
|
||||
throw APIErrors.SwitchNotFound;
|
||||
throw APIErrors.InvalidSwitchId;
|
||||
|
||||
var system = await ResolveSystem("@me");
|
||||
var sw = await _repo.GetSwitchByUuid(switchId);
|
||||
if (sw == null || system.Id != sw.System)
|
||||
throw APIErrors.SwitchNotFound;
|
||||
throw APIErrors.SwitchNotFoundPublic;
|
||||
|
||||
await _repo.DeleteSwitch(sw.Id);
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -46,7 +46,8 @@ namespace PluralKit.API
|
||||
public static PKError MemberNotFound = new(404, 20002, "Member not found.");
|
||||
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 SwitchNotFound = new(404, 20005, "Switch not found.");
|
||||
public static PKError SwitchNotFoundPublic = new(404, 20005, "Switch not found, switch associated with 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");
|
||||
@ -55,11 +56,15 @@ namespace PluralKit.API
|
||||
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, 30006, "Target member is not part of your system.");
|
||||
public static PKError NotOwnGroupError = new(403, 30006, "Target group is not part of your system.");
|
||||
public static PKError NotOwnGroupError = new(403, 30007, "Target group is not part of your system.");
|
||||
// todo: somehow add the memberRef to the JSON
|
||||
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 DuplicateMembersInList = new(400, 40003, "Duplicate members in member list.");
|
||||
public static PKError SameSwitchMembersError = new(400, 40004, "Member list identical to current fronter list.");
|
||||
public static PKError SameSwitchTimestampError = new(400, 40005, "Switch with provided timestamp already exists.");
|
||||
public static PKError InvalidSwitchId = new(400, 40006, "Invalid switch ID.");
|
||||
public static PKError Unimplemented = new(501, 50001, "Unimplemented");
|
||||
}
|
||||
}
|
@ -57,7 +57,12 @@ namespace PluralKit.API
|
||||
|
||||
services.AddControllers()
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Latest)
|
||||
.AddNewtonsoftJson(); // sorry MS, this just does *more*
|
||||
// sorry MS, this just does *more*
|
||||
.AddNewtonsoftJson((opts) =>
|
||||
{
|
||||
// ... though by default it messes up timestamps in JSON
|
||||
opts.SerializerSettings.DateParseHandling = DateParseHandling.None;
|
||||
});
|
||||
|
||||
services.AddApiVersioning();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user