feat(apiv2): switch endpoints

This commit is contained in:
spiral 2021-10-13 05:29:33 -04:00
parent f602f22a3d
commit 5add31c77e
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
5 changed files with 161 additions and 29 deletions

View File

@ -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; }
}
}

View File

@ -32,6 +32,7 @@ namespace PluralKit.API
public struct PostSwitchParams
{
public Instant? Timestamp { get; set; }
public ICollection<string> Members { get; set; }
}

View File

@ -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();
}
}
}

View File

@ -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");
}
}

View File

@ -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();