2021-04-21 21:57:19 +00:00
|
|
|
using System;
|
2019-07-09 18:39:29 +00:00
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Threading.Tasks;
|
2020-02-12 14:16:19 +00:00
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
using Dapper;
|
2020-01-11 15:49:20 +00:00
|
|
|
|
2020-06-15 23:15:59 +00:00
|
|
|
using Microsoft.AspNetCore.Authorization;
|
2020-01-11 15:49:20 +00:00
|
|
|
using Microsoft.AspNetCore.Http;
|
2019-07-09 18:39:29 +00:00
|
|
|
using Microsoft.AspNetCore.Mvc;
|
2020-02-12 14:16:19 +00:00
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
using Newtonsoft.Json;
|
2019-12-28 14:52:59 +00:00
|
|
|
using Newtonsoft.Json.Linq;
|
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
using NodaTime;
|
2020-02-12 14:16:19 +00:00
|
|
|
|
2019-07-09 22:21:00 +00:00
|
|
|
using PluralKit.Core;
|
2019-07-09 18:39:29 +00:00
|
|
|
|
2020-02-12 14:16:19 +00:00
|
|
|
namespace PluralKit.API
|
2019-07-09 18:39:29 +00:00
|
|
|
{
|
|
|
|
public struct SwitchesReturn
|
|
|
|
{
|
|
|
|
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
|
|
|
|
[JsonProperty("members")] public IEnumerable<string> Members { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
public struct FrontersReturn
|
|
|
|
{
|
|
|
|
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
|
2019-12-28 14:52:59 +00:00
|
|
|
[JsonProperty("members")] public IEnumerable<JObject> Members { get; set; }
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public struct PostSwitchParams
|
|
|
|
{
|
|
|
|
public ICollection<string> Members { get; set; }
|
|
|
|
}
|
|
|
|
|
|
|
|
[ApiController]
|
2020-05-07 02:39:49 +00:00
|
|
|
[ApiVersion("1.0")]
|
|
|
|
[Route( "v{version:apiVersion}/s" )]
|
2019-07-09 18:39:29 +00:00
|
|
|
public class SystemController : ControllerBase
|
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
private readonly IDatabase _db;
|
|
|
|
private readonly ModelRepository _repo;
|
|
|
|
private readonly IAuthorizationService _auth;
|
2019-07-09 18:39:29 +00:00
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
public SystemController(IDatabase db, IAuthorizationService auth, ModelRepository repo)
|
2019-07-09 18:39:29 +00:00
|
|
|
{
|
2020-06-29 12:39:19 +00:00
|
|
|
_db = db;
|
2019-07-09 18:39:29 +00:00
|
|
|
_auth = auth;
|
2020-08-29 11:46:27 +00:00
|
|
|
_repo = repo;
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
2019-07-15 18:06:28 +00:00
|
|
|
[HttpGet]
|
2020-06-15 23:15:59 +00:00
|
|
|
[Authorize]
|
|
|
|
public async Task<ActionResult<JObject>> GetOwnSystem()
|
2019-07-15 18:06:28 +00:00
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
var system = await _db.Execute(c => _repo.GetSystem(c, User.CurrentSystem()));
|
2020-06-15 23:15:59 +00:00
|
|
|
return system.ToJson(User.ContextFor(system));
|
2019-07-15 18:06:28 +00:00
|
|
|
}
|
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
[HttpGet("{hid}")]
|
2019-12-28 14:52:59 +00:00
|
|
|
public async Task<ActionResult<JObject>> GetSystem(string hid)
|
2019-07-09 18:39:29 +00:00
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
var system = await _db.Execute(c => _repo.GetSystemByHid(c, hid));
|
2019-07-09 18:39:29 +00:00
|
|
|
if (system == null) return NotFound("System not found.");
|
2020-06-15 23:15:59 +00:00
|
|
|
return Ok(system.ToJson(User.ContextFor(system)));
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpGet("{hid}/members")]
|
2019-12-28 14:52:59 +00:00
|
|
|
public async Task<ActionResult<IEnumerable<JObject>>> GetMembers(string hid)
|
2019-07-09 18:39:29 +00:00
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
var system = await _db.Execute(c => _repo.GetSystemByHid(c, hid));
|
|
|
|
if (system == null)
|
|
|
|
return NotFound("System not found.");
|
2019-07-09 18:39:29 +00:00
|
|
|
|
2020-06-15 23:15:59 +00:00
|
|
|
if (!system.MemberListPrivacy.CanAccess(User.ContextFor(system)))
|
2020-01-11 15:49:20 +00:00
|
|
|
return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view member list.");
|
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
var members = _db.Execute(c => _repo.GetSystemMembers(c, system.Id));
|
2020-01-17 23:58:35 +00:00
|
|
|
return Ok(await members
|
2020-06-17 19:31:39 +00:00
|
|
|
.Where(m => m.MemberVisibility.CanAccess(User.ContextFor(system)))
|
2020-06-15 23:15:59 +00:00
|
|
|
.Select(m => m.ToJson(User.ContextFor(system)))
|
2020-01-17 23:58:35 +00:00
|
|
|
.ToListAsync());
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpGet("{hid}/switches")]
|
|
|
|
public async Task<ActionResult<IEnumerable<SwitchesReturn>>> GetSwitches(string hid, [FromQuery(Name = "before")] Instant? before)
|
|
|
|
{
|
2019-07-09 22:23:41 +00:00
|
|
|
if (before == null) before = SystemClock.Instance.GetCurrentInstant();
|
2019-07-09 18:39:29 +00:00
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
await using var conn = await _db.Obtain();
|
|
|
|
|
|
|
|
var system = await _repo.GetSystemByHid(conn, hid);
|
2019-07-09 18:39:29 +00:00
|
|
|
if (system == null) return NotFound("System not found.");
|
2020-06-15 23:15:59 +00:00
|
|
|
|
|
|
|
var auth = await _auth.AuthorizeAsync(User, system, "ViewFrontHistory");
|
|
|
|
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view front history.");
|
2019-07-09 18:39:29 +00:00
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
var res = await conn.QueryAsync<SwitchesReturn>(
|
|
|
|
@"select *, array(
|
2019-07-09 18:39:29 +00:00
|
|
|
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
|
2019-07-11 19:25:23 +00:00
|
|
|
limit 100;", new {System = system.Id, Before = before});
|
2020-08-29 11:46:27 +00:00
|
|
|
return Ok(res);
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpGet("{hid}/fronters")]
|
|
|
|
public async Task<ActionResult<FrontersReturn>> GetFronters(string hid)
|
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
await using var conn = await _db.Obtain();
|
|
|
|
|
|
|
|
var system = await _repo.GetSystemByHid(conn, hid);
|
2019-07-09 18:39:29 +00:00
|
|
|
if (system == null) return NotFound("System not found.");
|
|
|
|
|
2020-06-15 23:15:59 +00:00
|
|
|
var auth = await _auth.AuthorizeAsync(User, system, "ViewFront");
|
|
|
|
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view fronter.");
|
2020-01-11 15:49:20 +00:00
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
var sw = await _repo.GetLatestSwitch(conn, system.Id);
|
2019-07-10 10:54:54 +00:00
|
|
|
if (sw == null) return NotFound("System has no registered switches.");
|
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
var members = _repo.GetSwitchMembers(conn, sw.Id);
|
2019-07-09 18:39:29 +00:00
|
|
|
return Ok(new FrontersReturn
|
|
|
|
{
|
|
|
|
Timestamp = sw.Timestamp,
|
2020-06-15 23:15:59 +00:00
|
|
|
Members = await members.Select(m => m.ToJson(User.ContextFor(system))).ToListAsync()
|
2019-07-09 18:39:29 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
[HttpPatch]
|
2020-06-15 23:15:59 +00:00
|
|
|
[Authorize]
|
2019-12-28 14:52:59 +00:00
|
|
|
public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes)
|
2019-07-09 18:39:29 +00:00
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
await using var conn = await _db.Obtain();
|
|
|
|
var system = await _repo.GetSystem(conn, User.CurrentSystem());
|
2020-06-29 12:39:19 +00:00
|
|
|
|
|
|
|
SystemPatch patch;
|
2019-12-28 14:52:59 +00:00
|
|
|
try
|
|
|
|
{
|
2020-06-29 12:39:19 +00:00
|
|
|
patch = JsonModelExt.ToSystemPatch(changes);
|
2021-04-21 21:57:19 +00:00
|
|
|
patch.CheckIsValid();
|
2019-12-28 14:52:59 +00:00
|
|
|
}
|
2020-02-12 14:16:19 +00:00
|
|
|
catch (JsonModelParseError e)
|
2019-12-28 14:52:59 +00:00
|
|
|
{
|
|
|
|
return BadRequest(e.Message);
|
|
|
|
}
|
2021-04-21 21:57:19 +00:00
|
|
|
catch (InvalidPatchException e)
|
|
|
|
{
|
|
|
|
return BadRequest($"Request field '{e.Message}' is invalid.");
|
|
|
|
}
|
2019-12-28 14:52:59 +00:00
|
|
|
|
2021-04-21 21:57:19 +00:00
|
|
|
system = await _repo.UpdateSystem(conn, system!.Id, patch);
|
2020-06-15 23:15:59 +00:00
|
|
|
return Ok(system.ToJson(User.ContextFor(system)));
|
2019-07-09 18:39:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[HttpPost("switches")]
|
2020-06-15 23:15:59 +00:00
|
|
|
[Authorize]
|
2019-07-09 18:39:29 +00:00
|
|
|
public async Task<IActionResult> PostSwitch([FromBody] PostSwitchParams param)
|
|
|
|
{
|
2020-06-15 23:15:59 +00:00
|
|
|
if (param.Members.Distinct().Count() != param.Members.Count)
|
2019-07-09 18:39:29 +00:00
|
|
|
return BadRequest("Duplicate members in member list.");
|
|
|
|
|
2020-08-29 11:46:27 +00:00
|
|
|
await using var conn = await _db.Obtain();
|
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
// We get the current switch, if it exists
|
2020-08-29 11:46:27 +00:00
|
|
|
var latestSwitch = await _repo.GetLatestSwitch(conn, User.CurrentSystem());
|
2019-07-18 10:09:19 +00:00
|
|
|
if (latestSwitch != null)
|
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
var latestSwitchMembers = _repo.GetSwitchMembers(conn, latestSwitch.Id);
|
2019-07-18 10:09:19 +00:00
|
|
|
|
|
|
|
// Bail if this switch is identical to the latest one
|
2020-01-17 23:02:17 +00:00
|
|
|
if (await latestSwitchMembers.Select(m => m.Hid).SequenceEqualAsync(param.Members.ToAsyncEnumerable()))
|
2019-07-18 10:09:19 +00:00
|
|
|
return BadRequest("New members identical to existing fronters.");
|
|
|
|
}
|
2019-07-09 18:39:29 +00:00
|
|
|
|
|
|
|
// Resolve member objects for all given IDs
|
2020-08-29 11:46:27 +00:00
|
|
|
var membersList = (await conn.QueryAsync<PKMember>("select * from members where hid = any(@Hids)", new {Hids = param.Members})).ToList();
|
2019-07-11 19:25:23 +00:00
|
|
|
|
2019-07-09 18:39:29 +00:00
|
|
|
foreach (var member in membersList)
|
2020-06-15 23:15:59 +00:00
|
|
|
if (member.System != User.CurrentSystem())
|
2019-07-09 18:39:29 +00:00
|
|
|
return BadRequest($"Cannot switch to member '{member.Hid}' not in system.");
|
|
|
|
|
|
|
|
// membersList is in DB order, and we want it in actual input order
|
|
|
|
// so we go through a dict and map the original input appropriately
|
|
|
|
var membersDict = membersList.ToDictionary(m => m.Hid);
|
|
|
|
|
|
|
|
var membersInOrder = new List<PKMember>();
|
|
|
|
// We do this without .Select() since we want to have the early return bail if it doesn't find the member
|
|
|
|
foreach (var givenMemberId in param.Members)
|
|
|
|
{
|
2020-08-29 11:46:27 +00:00
|
|
|
if (!membersDict.TryGetValue(givenMemberId, out var member))
|
|
|
|
return BadRequest($"Member '{givenMemberId}' not found.");
|
2019-07-09 18:39:29 +00:00
|
|
|
membersInOrder.Add(member);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, log the switch (yay!)
|
2020-08-29 11:46:27 +00:00
|
|
|
await _repo.AddSwitch(conn, User.CurrentSystem(), membersInOrder.Select(m => m.Id).ToList());
|
2019-07-09 18:39:29 +00:00
|
|
|
return NoContent();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|