Migrate API to ASP.NET Core Auth services + refactor
This commit is contained in:
33
PluralKit.API/Controllers/v1/AccountController.cs
Normal file
33
PluralKit.API/Controllers/v1/AccountController.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("a")]
|
||||
[Route( "v{version:apiVersion}/a" )]
|
||||
public class AccountController: ControllerBase
|
||||
{
|
||||
private IDataStore _data;
|
||||
|
||||
public AccountController(IDataStore data)
|
||||
{
|
||||
_data = data;
|
||||
}
|
||||
|
||||
[HttpGet("{aid}")]
|
||||
public async Task<ActionResult<JObject>> GetSystemByAccount(ulong aid)
|
||||
{
|
||||
var system = await _data.GetSystemByAccount(aid);
|
||||
if (system == null) return NotFound("Account not found.");
|
||||
|
||||
return Ok(system.ToJson(User.ContextFor(system)));
|
||||
}
|
||||
}
|
||||
}
|
131
PluralKit.API/Controllers/v1/JsonModelExt.cs
Normal file
131
PluralKit.API/Controllers/v1/JsonModelExt.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public static class JsonModelExt
|
||||
{
|
||||
public static JObject ToJson(this PKSystem system, LookupContext ctx)
|
||||
{
|
||||
var o = new JObject();
|
||||
o.Add("id", system.Hid);
|
||||
o.Add("name", system.Name);
|
||||
o.Add("description", system.DescriptionPrivacy.CanAccess(ctx) ? system.Description : null);
|
||||
o.Add("tag", system.Tag);
|
||||
o.Add("avatar_url", system.AvatarUrl);
|
||||
o.Add("created", DateTimeFormats.TimestampExportFormat.Format(system.Created));
|
||||
o.Add("tz", system.UiTz);
|
||||
o.Add("description_privacy", ctx == LookupContext.ByOwner ? system.DescriptionPrivacy.ToJsonString() : null);
|
||||
o.Add("member_list_privacy", ctx == LookupContext.ByOwner ? system.MemberListPrivacy.ToJsonString() : null);
|
||||
o.Add("front_privacy", ctx == LookupContext.ByOwner ? system.FrontPrivacy.ToJsonString() : null);
|
||||
o.Add("front_history_privacy", ctx == LookupContext.ByOwner ? ctx == LookupContext.ByOwner ? system.FrontHistoryPrivacy.ToJsonString() : null : null);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static void ApplyJson(this PKSystem system, JObject o)
|
||||
{
|
||||
if (o.ContainsKey("name")) system.Name = o.Value<string>("name").NullIfEmpty().BoundsCheckField(Limits.MaxSystemNameLength, "System name");
|
||||
if (o.ContainsKey("description")) system.Description = o.Value<string>("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "System description");
|
||||
if (o.ContainsKey("tag")) system.Tag = o.Value<string>("tag").NullIfEmpty().BoundsCheckField(Limits.MaxSystemTagLength, "System tag");
|
||||
if (o.ContainsKey("avatar_url")) system.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "System avatar URL");
|
||||
if (o.ContainsKey("tz")) system.UiTz = o.Value<string>("tz") ?? "UTC";
|
||||
|
||||
if (o.ContainsKey("description_privacy")) system.DescriptionPrivacy = o.Value<string>("description_privacy").ParsePrivacy("description");
|
||||
if (o.ContainsKey("member_list_privacy")) system.MemberListPrivacy = o.Value<string>("member_list_privacy").ParsePrivacy("member list");
|
||||
if (o.ContainsKey("front_privacy")) system.FrontPrivacy = o.Value<string>("front_privacy").ParsePrivacy("front");
|
||||
if (o.ContainsKey("front_history_privacy")) system.FrontHistoryPrivacy = o.Value<string>("front_history_privacy").ParsePrivacy("front history");
|
||||
}
|
||||
|
||||
public static JObject ToJson(this PKMember member, LookupContext ctx)
|
||||
{
|
||||
var o = new JObject();
|
||||
o.Add("id", member.Hid);
|
||||
o.Add("name", member.Name);
|
||||
o.Add("color", member.MemberPrivacy.CanAccess(ctx) ? member.Color : null);
|
||||
o.Add("display_name", member.DisplayName);
|
||||
o.Add("birthday", member.MemberPrivacy.CanAccess(ctx) && member.Birthday.HasValue ? DateTimeFormats.DateExportFormat.Format(member.Birthday.Value) : null);
|
||||
o.Add("pronouns", member.MemberPrivacy.CanAccess(ctx) ? member.Pronouns : null);
|
||||
o.Add("avatar_url", member.AvatarUrl);
|
||||
o.Add("description", member.MemberPrivacy.CanAccess(ctx) ? member.Description : null);
|
||||
o.Add("privacy", ctx == LookupContext.ByOwner ? (member.MemberPrivacy == PrivacyLevel.Private ? "private" : "public") : null);
|
||||
|
||||
var tagArray = new JArray();
|
||||
foreach (var tag in member.ProxyTags)
|
||||
tagArray.Add(new JObject {{"prefix", tag.Prefix}, {"suffix", tag.Suffix}});
|
||||
o.Add("proxy_tags", tagArray);
|
||||
|
||||
o.Add("keep_proxy", member.KeepProxy);
|
||||
o.Add("created", DateTimeFormats.TimestampExportFormat.Format(member.Created));
|
||||
|
||||
if (member.ProxyTags.Count > 0)
|
||||
{
|
||||
// Legacy compatibility only, TODO: remove at some point
|
||||
o.Add("prefix", member.ProxyTags?.FirstOrDefault().Prefix);
|
||||
o.Add("suffix", member.ProxyTags?.FirstOrDefault().Suffix);
|
||||
}
|
||||
|
||||
return o;
|
||||
}
|
||||
|
||||
public static void ApplyJson(this PKMember member, JObject o)
|
||||
{
|
||||
if (o.ContainsKey("name") && o["name"].Type == JTokenType.Null)
|
||||
throw new JsonModelParseError("Member name can not be set to null.");
|
||||
|
||||
if (o.ContainsKey("name")) member.Name = o.Value<string>("name").BoundsCheckField(Limits.MaxMemberNameLength, "Member name");
|
||||
if (o.ContainsKey("color")) member.Color = o.Value<string>("color").NullIfEmpty()?.ToLower();
|
||||
if (o.ContainsKey("display_name")) member.DisplayName = o.Value<string>("display_name").NullIfEmpty().BoundsCheckField(Limits.MaxMemberNameLength, "Member display name");
|
||||
if (o.ContainsKey("avatar_url")) member.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty().BoundsCheckField(Limits.MaxUriLength, "Member avatar URL");
|
||||
if (o.ContainsKey("birthday"))
|
||||
{
|
||||
var str = o.Value<string>("birthday").NullIfEmpty();
|
||||
var res = DateTimeFormats.DateExportFormat.Parse(str);
|
||||
if (res.Success) member.Birthday = res.Value;
|
||||
else if (str == null) member.Birthday = null;
|
||||
else throw new JsonModelParseError("Could not parse member birthday.");
|
||||
}
|
||||
|
||||
if (o.ContainsKey("pronouns")) member.Pronouns = o.Value<string>("pronouns").NullIfEmpty().BoundsCheckField(Limits.MaxPronounsLength, "Member pronouns");
|
||||
if (o.ContainsKey("description")) member.Description = o.Value<string>("description").NullIfEmpty().BoundsCheckField(Limits.MaxDescriptionLength, "Member descriptoin");
|
||||
if (o.ContainsKey("keep_proxy")) member.KeepProxy = o.Value<bool>("keep_proxy");
|
||||
|
||||
if (o.ContainsKey("prefix") || o.ContainsKey("suffix") && !o.ContainsKey("proxy_tags"))
|
||||
member.ProxyTags = new[] {new ProxyTag(o.Value<string>("prefix"), o.Value<string>("suffix"))};
|
||||
else if (o.ContainsKey("proxy_tags"))
|
||||
{
|
||||
member.ProxyTags = o.Value<JArray>("proxy_tags")
|
||||
.OfType<JObject>().Select(o => new ProxyTag(o.Value<string>("prefix"), o.Value<string>("suffix")))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (o.ContainsKey("privacy")) member.MemberPrivacy = o.Value<string>("privacy").ParsePrivacy("member");
|
||||
}
|
||||
|
||||
private static string BoundsCheckField(this string input, int maxLength, string nameInError)
|
||||
{
|
||||
if (input != null && input.Length > maxLength)
|
||||
throw new JsonModelParseError($"{nameInError} too long ({input.Length} > {maxLength}).");
|
||||
return input;
|
||||
}
|
||||
|
||||
private static string ToJsonString(this PrivacyLevel level) => level == PrivacyLevel.Private ? "private" : "public";
|
||||
|
||||
private static PrivacyLevel ParsePrivacy(this string input, string errorName)
|
||||
{
|
||||
if (input == null) return PrivacyLevel.Private;
|
||||
if (input == "") return PrivacyLevel.Private;
|
||||
if (input == "private") return PrivacyLevel.Private;
|
||||
if (input == "public") return PrivacyLevel.Public;
|
||||
throw new JsonModelParseError($"Could not parse {errorName} privacy.");
|
||||
}
|
||||
}
|
||||
|
||||
public class JsonModelParseError: Exception
|
||||
{
|
||||
public JsonModelParseError(string message): base(message) { }
|
||||
}
|
||||
}
|
101
PluralKit.API/Controllers/v1/MemberController.cs
Normal file
101
PluralKit.API/Controllers/v1/MemberController.cs
Normal file
@@ -0,0 +1,101 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("m")]
|
||||
[Route( "v{version:apiVersion}/m" )]
|
||||
public class MemberController: ControllerBase
|
||||
{
|
||||
private IDataStore _data;
|
||||
private IAuthorizationService _auth;
|
||||
|
||||
public MemberController(IDataStore data, IAuthorizationService auth)
|
||||
{
|
||||
_data = data;
|
||||
_auth = auth;
|
||||
}
|
||||
|
||||
[HttpGet("{hid}")]
|
||||
public async Task<ActionResult<JObject>> GetMember(string hid)
|
||||
{
|
||||
var member = await _data.GetMemberByHid(hid);
|
||||
if (member == null) return NotFound("Member not found.");
|
||||
|
||||
return Ok(member.ToJson(User.ContextFor(member)));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<JObject>> PostMember([FromBody] JObject properties)
|
||||
{
|
||||
var system = User.CurrentSystem();
|
||||
|
||||
if (!properties.ContainsKey("name"))
|
||||
return BadRequest("Member name must be specified.");
|
||||
|
||||
// Enforce per-system member limit
|
||||
var memberCount = await _data.GetSystemMemberCount(system, true);
|
||||
if (memberCount >= Limits.MaxMemberCount)
|
||||
return BadRequest($"Member limit reached ({memberCount} / {Limits.MaxMemberCount}).");
|
||||
|
||||
var member = await _data.CreateMember(system, properties.Value<string>("name"));
|
||||
try
|
||||
{
|
||||
member.ApplyJson(properties);
|
||||
}
|
||||
catch (JsonModelParseError e)
|
||||
{
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
|
||||
await _data.SaveMember(member);
|
||||
return Ok(member.ToJson(User.ContextFor(member)));
|
||||
}
|
||||
|
||||
[HttpPatch("{hid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<JObject>> PatchMember(string hid, [FromBody] JObject changes)
|
||||
{
|
||||
var member = await _data.GetMemberByHid(hid);
|
||||
if (member == null) return NotFound("Member not found.");
|
||||
|
||||
var res = await _auth.AuthorizeAsync(User, member, "EditMember");
|
||||
if (!res.Succeeded) return Unauthorized($"Member '{hid}' is not part of your system.");
|
||||
|
||||
try
|
||||
{
|
||||
member.ApplyJson(changes);
|
||||
}
|
||||
catch (JsonModelParseError e)
|
||||
{
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
|
||||
await _data.SaveMember(member);
|
||||
return Ok(member.ToJson(User.ContextFor(member)));
|
||||
}
|
||||
|
||||
[HttpDelete("{hid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> DeleteMember(string hid)
|
||||
{
|
||||
var member = await _data.GetMemberByHid(hid);
|
||||
if (member == null) return NotFound("Member not found.");
|
||||
|
||||
var res = await _auth.AuthorizeAsync(User, member, "EditMember");
|
||||
if (!res.Succeeded) return Unauthorized($"Member '{hid}' is not part of your system.");
|
||||
|
||||
await _data.DeleteMember(member);
|
||||
return Ok();
|
||||
}
|
||||
}
|
||||
}
|
57
PluralKit.API/Controllers/v1/MessageController.cs
Normal file
57
PluralKit.API/Controllers/v1/MessageController.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public struct MessageReturn
|
||||
{
|
||||
[JsonProperty("timestamp")] public Instant Timestamp;
|
||||
[JsonProperty("id")] public string Id;
|
||||
[JsonProperty("original")] public string Original;
|
||||
[JsonProperty("sender")] public string Sender;
|
||||
[JsonProperty("channel")] public string Channel;
|
||||
|
||||
[JsonProperty("system")] public JObject System;
|
||||
[JsonProperty("member")] public JObject Member;
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[ApiVersion("1.0")]
|
||||
[Route("msg")]
|
||||
[Route( "v{version:apiVersion}/msg" )]
|
||||
public class MessageController: ControllerBase
|
||||
{
|
||||
private IDataStore _data;
|
||||
|
||||
public MessageController(IDataStore _data)
|
||||
{
|
||||
this._data = _data;
|
||||
}
|
||||
|
||||
[HttpGet("{mid}")]
|
||||
public async Task<ActionResult<MessageReturn>> GetMessage(ulong mid)
|
||||
{
|
||||
var msg = await _data.GetMessage(mid);
|
||||
if (msg == null) return NotFound("Message not found.");
|
||||
|
||||
return new MessageReturn
|
||||
{
|
||||
Timestamp = Instant.FromUnixTimeMilliseconds((long) (msg.Message.Mid >> 22) + 1420070400000),
|
||||
Id = msg.Message.Mid.ToString(),
|
||||
Channel = msg.Message.Channel.ToString(),
|
||||
Sender = msg.Message.Sender.ToString(),
|
||||
Member = msg.Member.ToJson(User.ContextFor(msg.System)),
|
||||
System = msg.System.ToJson(User.ContextFor(msg.System)),
|
||||
Original = msg.Message.OriginalMid?.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
193
PluralKit.API/Controllers/v1/SystemController.cs
Normal file
193
PluralKit.API/Controllers/v1/SystemController.cs
Normal file
@@ -0,0 +1,193 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dapper;
|
||||
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
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")]
|
||||
[Route("s")]
|
||||
[Route( "v{version:apiVersion}/s" )]
|
||||
public class SystemController : ControllerBase
|
||||
{
|
||||
private IDataStore _data;
|
||||
private IDatabase _conn;
|
||||
private IAuthorizationService _auth;
|
||||
|
||||
public SystemController(IDataStore data, IDatabase conn, IAuthorizationService auth)
|
||||
{
|
||||
_data = data;
|
||||
_conn = conn;
|
||||
_auth = auth;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<JObject>> GetOwnSystem()
|
||||
{
|
||||
var system = await _conn.Execute(c => c.QuerySystem(User.CurrentSystem()));
|
||||
return system.ToJson(User.ContextFor(system));
|
||||
}
|
||||
|
||||
[HttpGet("{hid}")]
|
||||
public async Task<ActionResult<JObject>> GetSystem(string hid)
|
||||
{
|
||||
var system = await _data.GetSystemByHid(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)
|
||||
{
|
||||
var system = await _data.GetSystemByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
|
||||
if (!system.MemberListPrivacy.CanAccess(User.ContextFor(system)))
|
||||
return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view member list.");
|
||||
|
||||
var members = _data.GetSystemMembers(system);
|
||||
return Ok(await members
|
||||
.Where(m => m.MemberPrivacy.CanAccess(User.ContextFor(system)))
|
||||
.Select(m => m.ToJson(User.ContextFor(system)))
|
||||
.ToListAsync());
|
||||
}
|
||||
|
||||
[HttpGet("{hid}/switches")]
|
||||
public async Task<ActionResult<IEnumerable<SwitchesReturn>>> GetSwitches(string hid, [FromQuery(Name = "before")] Instant? before)
|
||||
{
|
||||
if (before == null) before = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var system = await _data.GetSystemByHid(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.");
|
||||
|
||||
using (var conn = await _conn.Obtain())
|
||||
{
|
||||
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
|
||||
limit 100;", new {System = system.Id, Before = before});
|
||||
return Ok(res);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("{hid}/fronters")]
|
||||
public async Task<ActionResult<FrontersReturn>> GetFronters(string hid)
|
||||
{
|
||||
var system = await _data.GetSystemByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
|
||||
var auth = await _auth.AuthorizeAsync(User, system, "ViewFront");
|
||||
if (!auth.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, "Unauthorized to view fronter.");
|
||||
|
||||
var sw = await _data.GetLatestSwitch(system.Id);
|
||||
if (sw == null) return NotFound("System has no registered switches.");
|
||||
|
||||
var members = _data.GetSwitchMembers(sw);
|
||||
return Ok(new FrontersReturn
|
||||
{
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = await members.Select(m => m.ToJson(User.ContextFor(system))).ToListAsync()
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
[Authorize]
|
||||
public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes)
|
||||
{
|
||||
var system = await _conn.Execute(c => c.QuerySystem(User.CurrentSystem()));
|
||||
try
|
||||
{
|
||||
system.ApplyJson(changes);
|
||||
}
|
||||
catch (JsonModelParseError e)
|
||||
{
|
||||
return BadRequest(e.Message);
|
||||
}
|
||||
|
||||
await _data.SaveSystem(system);
|
||||
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.");
|
||||
|
||||
// We get the current switch, if it exists
|
||||
var latestSwitch = await _data.GetLatestSwitch(User.CurrentSystem());
|
||||
if (latestSwitch != null)
|
||||
{
|
||||
var latestSwitchMembers = _data.GetSwitchMembers(latestSwitch);
|
||||
|
||||
// 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
|
||||
IEnumerable<PKMember> membersList;
|
||||
using (var conn = await _conn.Obtain())
|
||||
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);
|
||||
|
||||
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)
|
||||
{
|
||||
if (!membersDict.TryGetValue(givenMemberId, out var member)) return BadRequest($"Member '{givenMemberId}' not found.");
|
||||
membersInOrder.Add(member);
|
||||
}
|
||||
|
||||
// Finally, log the switch (yay!)
|
||||
await _data.AddSwitch(User.CurrentSystem(), membersInOrder);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user