PluralKit/PluralKit.API/Controllers/v1/SystemController.cs

204 lines
7.9 KiB
C#
Raw Normal View History

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
2020-01-11 15:49:20 +00:00
using Microsoft.AspNetCore.Authorization;
2020-01-11 15:49:20 +00:00
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NodaTime;
2019-07-09 22:21:00 +00:00
using PluralKit.Core;
namespace PluralKit.API
{
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; }
[JsonProperty("members")] public IEnumerable<JObject> Members { get; set; }
}
public struct PostSwitchParams
{
public ICollection<string> Members { get; set; }
}
[ApiController]
[ApiVersion("1.0")]
2021-08-27 15:03:47 +00:00
[Route("v{version:apiVersion}/s")]
public class SystemController: ControllerBase
{
2020-08-29 11:46:27 +00:00
private readonly IDatabase _db;
private readonly ModelRepository _repo;
private readonly IAuthorizationService _auth;
2020-08-29 11:46:27 +00:00
public SystemController(IDatabase db, IAuthorizationService auth, ModelRepository repo)
{
_db = db;
_auth = auth;
2020-08-29 11:46:27 +00:00
_repo = repo;
}
[HttpGet]
[Authorize]
public async Task<ActionResult<JObject>> GetOwnSystem()
{
2020-08-29 11:46:27 +00:00
var system = await _db.Execute(c => _repo.GetSystem(c, User.CurrentSystem()));
return system.ToJson(User.ContextFor(system));
}
[HttpGet("{hid}")]
public async Task<ActionResult<JObject>> GetSystem(string hid)
{
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.");
return Ok(system.ToJson(User.ContextFor(system)));
}
[HttpGet("{hid}/members")]
public async Task<ActionResult<IEnumerable<JObject>>> GetMembers(string hid)
{
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.");
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));
return Ok(await members
Feature/granular member privacy (#174) * Some reasons this needs to exist for it to run on my machine? I don't think it would hurt to have it in other machines so * Add options to member model * Add Privacy to member embed * Added member privacy display list * Update database settings * apparetnly this is nolonger needed? * Fix sql call * Fix more sql errors * Added in settings control * Add all subject to system privacy * Basic API Privacy * Name privacy in logs * update todo * remove CheckReadMemberPermission * Added name privacy to log embed * update todo * Update todo * Update api to handle privacy * update todo * Update systemlist full to respect privacy (as well as system list) * include colour as option for member privacy subject * move todo file (why was it there?) * Update TODO.md * Update TODO.md * Update TODO.md * Deleted to create pr * Update command usage and add to the command tree * Make api respect created privacy * Add editing privacy through the api * Fix pronoun privacy field in api * Fix info leak of display name in api * deprecate privacy field in api * Deprecate privacy diffrently * Update API * Update documentation * Update documentation * Remove comment in yml * Update userguide * Update migration (fix typo in 5.sql too) * Sanatize names * some full stops * Fix after merge * update migration * update schema version * update edit command * update privacy filter * fix a dumb mistake * clarify on what name privacy does * make it easier on someone else * Update docs * Comment out unused code * Add aliases for `member privacy all public` and `member privacy all private`
2020-06-17 19:31:39 +00:00
.Where(m => m.MemberVisibility.CanAccess(User.ContextFor(system)))
.Select(m => m.ToJson(User.ContextFor(system), needsLegacyProxyTags: true))
.ToListAsync());
}
[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();
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
await using var conn = await _db.Obtain();
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
var system = await _repo.GetSystemByHid(conn, hid);
if (system == null) return NotFound("System not found.");
var auth = await _auth.AuthorizeAsync(User, system, "ViewFrontHistory");
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view front history.");
2020-08-29 11:46:27 +00:00
var res = await conn.QueryAsync<SwitchesReturn>(
@"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
2021-08-27 15:03:47 +00:00
limit 100;", new { System = system.Id, Before = before });
2020-08-29 11:46:27 +00:00
return Ok(res);
}
[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();
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
var system = await _repo.GetSystemByHid(conn, hid);
if (system == null) return NotFound("System not found.");
2021-08-27 15:03:47 +00:00
var auth = await _auth.AuthorizeAsync(User, system, "ViewFront");
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view fronter.");
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
var sw = await _repo.GetLatestSwitch(conn, system.Id);
2021-08-27 15:03:47 +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);
return Ok(new FrontersReturn
{
Timestamp = sw.Timestamp,
Members = await members.Select(m => m.ToJson(User.ContextFor(system), needsLegacyProxyTags: true)).ToListAsync()
});
}
[HttpPatch]
[Authorize]
public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes)
{
2020-08-29 11:46:27 +00:00
await using var conn = await _db.Obtain();
var system = await _repo.GetSystem(conn, User.CurrentSystem());
SystemPatch patch;
try
{
patch = SystemPatch.FromJSON(changes);
patch.AssertIsValid();
}
catch (FieldTooLongError e)
{
return BadRequest(e.Message);
}
catch (ValidationError e)
{
return BadRequest($"Request field '{e.Message}' is invalid.");
}
system = await _repo.UpdateSystem(conn, system!.Id, patch);
return Ok(system.ToJson(User.ContextFor(system)));
}
[HttpPost("switches")]
[Authorize]
public async Task<IActionResult> PostSwitch([FromBody] PostSwitchParams param)
{
if (param.Members.Distinct().Count() != param.Members.Count)
return BadRequest("Duplicate members in member list.");
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
await using var conn = await _db.Obtain();
// We get the current switch, if it exists
2020-08-29 11:46:27 +00:00
var latestSwitch = await _repo.GetLatestSwitch(conn, User.CurrentSystem());
if (latestSwitch != null)
{
2020-08-29 11:46:27 +00:00
var latestSwitchMembers = _repo.GetSwitchMembers(conn, latestSwitch.Id);
// Bail if this switch is identical to the latest one
if (await latestSwitchMembers.Select(m => m.Hid).SequenceEqualAsync(param.Members.ToAsyncEnumerable()))
return BadRequest("New members identical to existing fronters.");
}
// Resolve member objects for all given IDs
2021-08-27 15:03:47 +00:00
var membersList = (await conn.QueryAsync<PKMember>("select * from members where hid = any(@Hids)", new { Hids = param.Members })).ToList();
foreach (var member in membersList)
if (member.System != User.CurrentSystem())
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);
2021-08-27 15:03:47 +00:00
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)
{
2021-08-27 15:03:47 +00:00
if (!membersDict.TryGetValue(givenMemberId, out var member))
2020-08-29 11:46:27 +00:00
return BadRequest($"Member '{givenMemberId}' not found.");
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());
return NoContent();
}
}
}