2021-10-12 09:17:54 +00:00
|
|
|
using Dapper;
|
|
|
|
|
2021-09-30 02:30:20 +00:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
|
|
|
|
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
2021-10-12 09:17:54 +00:00
|
|
|
using NodaTime;
|
|
|
|
|
2021-09-30 02:30:20 +00:00
|
|
|
using PluralKit.Core;
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
namespace PluralKit.API;
|
|
|
|
|
|
|
|
[ApiController]
|
|
|
|
[ApiVersion("2.0")]
|
|
|
|
[Route("v{version:apiVersion}")]
|
|
|
|
public class SwitchControllerV2: PKControllerBase
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
public SwitchControllerV2(IServiceProvider svc) : base(svc) { }
|
|
|
|
|
|
|
|
|
|
|
|
[HttpGet("systems/{systemRef}/switches")]
|
|
|
|
public async Task<IActionResult> GetSystemSwitches(string systemRef,
|
|
|
|
[FromQuery(Name = "before")] Instant? before,
|
|
|
|
[FromQuery(Name = "limit")] int? limit)
|
|
|
|
{
|
|
|
|
var system = await ResolveSystem(systemRef);
|
|
|
|
if (system == null)
|
|
|
|
throw Errors.SystemNotFound;
|
|
|
|
|
|
|
|
var ctx = ContextFor(system);
|
|
|
|
|
|
|
|
if (!system.FrontHistoryPrivacy.CanAccess(ctx))
|
|
|
|
throw Errors.UnauthorizedFrontHistory;
|
|
|
|
|
|
|
|
if (before == null)
|
|
|
|
before = SystemClock.Instance.GetCurrentInstant();
|
|
|
|
|
|
|
|
if (limit == null || limit > 100)
|
|
|
|
limit = 100;
|
|
|
|
|
|
|
|
var res = await _db.Execute(conn => conn.QueryAsync<SwitchesReturnNew>(
|
|
|
|
@"select *, array(
|
|
|
|
select members.hid from switch_members, members
|
|
|
|
where switch_members.switch = switches.id and members.id = switch_members.member
|
|
|
|
) as members from switches
|
|
|
|
where switches.system = @System and switches.timestamp < @Before
|
|
|
|
order by switches.timestamp desc
|
|
|
|
limit @Limit;",
|
|
|
|
new { System = system.Id, Before = before, Limit = limit }
|
|
|
|
));
|
|
|
|
return Ok(res);
|
|
|
|
}
|
|
|
|
|
|
|
|
[HttpGet("systems/{systemRef}/fronters")]
|
|
|
|
public async Task<IActionResult> GetSystemFronters(string systemRef)
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem(systemRef);
|
|
|
|
if (system == null)
|
|
|
|
throw Errors.SystemNotFound;
|
|
|
|
|
|
|
|
var ctx = ContextFor(system);
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (!system.FrontPrivacy.CanAccess(ctx))
|
|
|
|
throw Errors.UnauthorizedCurrentFronters;
|
|
|
|
|
|
|
|
var sw = await _repo.GetLatestSwitch(system.Id);
|
|
|
|
if (sw == null)
|
|
|
|
return NoContent();
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
|
|
|
return Ok(new FrontersReturnNew
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Timestamp = sw.Timestamp,
|
|
|
|
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync(),
|
|
|
|
Uuid = sw.Uuid,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
[HttpPost("systems/@me/switches")]
|
|
|
|
public async Task<IActionResult> SwitchCreate([FromBody] PostSwitchParams data)
|
|
|
|
{
|
|
|
|
if (data.Members.Distinct().Count() != data.Members.Count)
|
|
|
|
throw Errors.DuplicateMembersInList;
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem("@me");
|
|
|
|
|
|
|
|
if (data.Timestamp != null && await _repo.GetSwitches(system.Id).Select(x => x.Timestamp)
|
|
|
|
.ContainsAsync(data.Timestamp.Value))
|
|
|
|
throw Errors.SameSwitchTimestampError;
|
|
|
|
|
|
|
|
var members = new List<PKMember>();
|
|
|
|
|
|
|
|
foreach (var memberRef in data.Members)
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
var member = await ResolveMember(memberRef);
|
|
|
|
if (member == null)
|
2021-11-27 03:02:58 +00:00
|
|
|
throw Errors.MemberNotFoundWithRef(memberRef);
|
2021-11-27 02:10:56 +00:00
|
|
|
if (member.System != system.Id)
|
|
|
|
throw Errors.NotOwnMemberErrorWithRef(memberRef);
|
|
|
|
members.Add(member);
|
2021-09-30 02:30:20 +00:00
|
|
|
}
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
// 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))
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
var latestSwitchMembers = _db.Execute(conn => _repo.GetSwitchMembers(conn, latestSwitch.Id));
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
// 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 Errors.SameSwitchMembersError;
|
2021-09-30 02:30:20 +00:00
|
|
|
}
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
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);
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
return Ok(new FrontersReturnNew
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Uuid = newSwitch.Uuid,
|
|
|
|
Timestamp = data.Timestamp != null ? data.Timestamp.Value : newSwitch.Timestamp,
|
|
|
|
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2)),
|
|
|
|
});
|
|
|
|
}
|
2021-10-12 09:17:54 +00:00
|
|
|
|
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
[HttpGet("systems/{systemRef}/switches/{switchRef}")]
|
|
|
|
public async Task<IActionResult> SwitchGet(string systemRef, string switchRef)
|
|
|
|
{
|
|
|
|
if (!Guid.TryParse(switchRef, out var switchId))
|
|
|
|
throw Errors.InvalidSwitchId;
|
2021-10-12 10:18:54 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem(systemRef);
|
|
|
|
if (system == null)
|
|
|
|
throw Errors.SystemNotFound;
|
2021-10-12 09:17:54 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var sw = await _repo.GetSwitchByUuid(switchId);
|
|
|
|
if (sw == null || system.Id != sw.System)
|
|
|
|
throw Errors.SwitchNotFoundPublic;
|
2021-10-12 09:17:54 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var ctx = ContextFor(system);
|
|
|
|
|
|
|
|
if (!system.FrontHistoryPrivacy.CanAccess(ctx))
|
|
|
|
throw Errors.SwitchNotFoundPublic;
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
|
|
|
return Ok(new FrontersReturnNew
|
2021-10-13 09:29:33 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Uuid = sw.Uuid,
|
|
|
|
Timestamp = sw.Timestamp,
|
|
|
|
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
|
|
|
|
});
|
|
|
|
}
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
[HttpPatch("systems/@me/switches/{switchRef}")]
|
|
|
|
public async Task<IActionResult> SwitchPatch(string switchRef, [FromBody] JObject data)
|
|
|
|
{
|
|
|
|
// for now, don't need to make a PatchObject for this, since it's only one param
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (!Guid.TryParse(switchRef, out var switchId))
|
|
|
|
throw Errors.InvalidSwitchId;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var valueStr = data.Value<string>("timestamp").NullIfEmpty();
|
|
|
|
if (valueStr == null)
|
|
|
|
throw new ModelParseError(new List<ValidationError> { new("timestamp", "Key 'timestamp' is required.") });
|
2021-10-13 12:59:42 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var value = Instant.FromDateTimeOffset(DateTime.Parse(valueStr).ToUniversalTime());
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem("@me");
|
|
|
|
if (system == null)
|
|
|
|
throw Errors.SystemNotFound;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var sw = await _repo.GetSwitchByUuid(switchId);
|
|
|
|
if (sw == null || system.Id != sw.System)
|
|
|
|
throw Errors.SwitchNotFoundPublic;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (await _repo.GetSwitches(system.Id).Select(x => x.Timestamp).ContainsAsync(value))
|
|
|
|
throw Errors.SameSwitchTimestampError;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
await _repo.MoveSwitch(sw.Id, value);
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var members = await _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id)).ToListAsync();
|
|
|
|
return Ok(new FrontersReturnNew
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Uuid = sw.Uuid,
|
|
|
|
Timestamp = sw.Timestamp,
|
|
|
|
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2))
|
|
|
|
});
|
|
|
|
}
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
[HttpPatch("systems/@me/switches/{switchRef}/members")]
|
|
|
|
public async Task<IActionResult> SwitchMemberPatch(string switchRef, [FromBody] JArray data)
|
|
|
|
{
|
|
|
|
if (!Guid.TryParse(switchRef, out var switchId))
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (data.Distinct().Count() != data.Count)
|
|
|
|
throw Errors.DuplicateMembersInList;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem("@me");
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var sw = await _repo.GetSwitchByUuid(switchId);
|
|
|
|
if (sw == null)
|
|
|
|
throw Errors.SwitchNotFound;
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var members = new List<PKMember>();
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
foreach (var JmemberRef in data)
|
|
|
|
{
|
|
|
|
var memberRef = JmemberRef.Value<string>();
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var member = await ResolveMember(memberRef);
|
|
|
|
if (member == null)
|
2021-11-27 03:02:58 +00:00
|
|
|
throw Errors.MemberNotFoundWithRef(memberRef);
|
2021-11-27 02:10:56 +00:00
|
|
|
if (member.System != system.Id)
|
|
|
|
throw Errors.NotOwnMemberErrorWithRef(memberRef);
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
members.Add(member);
|
|
|
|
}
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var latestSwitchMembers = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
|
2021-10-13 09:29:33 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
if (await latestSwitchMembers.Select(m => m.Hid)
|
|
|
|
.SequenceEqualAsync(members.Select(m => m.Hid).ToAsyncEnumerable()))
|
|
|
|
throw Errors.SameSwitchMembersError;
|
2021-09-30 02:30:20 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
await _db.Execute(conn => _repo.EditSwitch(conn, sw.Id, members.Select(x => x.Id).ToList()));
|
|
|
|
return Ok(new FrontersReturnNew
|
2021-09-30 02:30:20 +00:00
|
|
|
{
|
2021-11-27 02:10:56 +00:00
|
|
|
Uuid = sw.Uuid,
|
|
|
|
Timestamp = sw.Timestamp,
|
|
|
|
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2))
|
|
|
|
});
|
|
|
|
}
|
2021-10-12 10:41:38 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
[HttpDelete("systems/@me/switches/{switchRef}")]
|
|
|
|
public async Task<IActionResult> SwitchDelete(string switchRef)
|
|
|
|
{
|
|
|
|
if (!Guid.TryParse(switchRef, out var switchId))
|
|
|
|
throw Errors.InvalidSwitchId;
|
2021-10-12 10:41:38 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
var system = await ResolveSystem("@me");
|
|
|
|
var sw = await _repo.GetSwitchByUuid(switchId);
|
|
|
|
if (sw == null || system.Id != sw.System)
|
|
|
|
throw Errors.SwitchNotFoundPublic;
|
2021-10-12 10:41:38 +00:00
|
|
|
|
2021-11-27 02:10:56 +00:00
|
|
|
await _repo.DeleteSwitch(sw.Id);
|
|
|
|
|
|
|
|
return NoContent();
|
2021-09-30 02:30:20 +00:00
|
|
|
}
|
|
|
|
}
|