chore: remove API v1

This commit is contained in:
spiral
2022-04-20 12:20:03 -04:00
parent 1e86c2d6c4
commit 7aaad288e6
29 changed files with 136 additions and 808 deletions

View File

@@ -60,7 +60,7 @@ public class PrivateController: PKControllerBase
var data = new JObject();
data.Add("privacy", inner);
var patch = MemberPatch.FromJSON(data, APIVersion.V2);
var patch = MemberPatch.FromJSON(data);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
@@ -135,7 +135,7 @@ public class PrivateController: PKControllerBase
var o = new JObject();
o.Add("system", system.ToJson(LookupContext.ByOwner, APIVersion.V2));
o.Add("system", system.ToJson(LookupContext.ByOwner));
o.Add("user", user);
o.Add("token", system.Token);

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using PluralKit.Core;
namespace PluralKit.API;
[ApiController]
[Route("v1/a")]
public class AccountController: ControllerBase
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
public AccountController(IDatabase db, ModelRepository repo)
{
_db = db;
_repo = repo;
}
[HttpGet("{aid}")]
public async Task<ActionResult<JObject>> GetSystemByAccount(ulong aid)
{
var system = await _repo.GetSystemByAccount(aid);
if (system == null)
return NotFound("Account not found.");
return Ok(system.ToJson(User.ContextFor(system)));
}
}

View File

@@ -1,120 +0,0 @@
using Dapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using PluralKit.Core;
namespace PluralKit.API;
[ApiController]
[Route("v1/m")]
public class MemberController: ControllerBase
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
private readonly IAuthorizationService _auth;
public MemberController(IAuthorizationService auth, IDatabase db, ModelRepository repo)
{
_auth = auth;
_db = db;
_repo = repo;
}
[HttpGet("{hid}")]
public async Task<ActionResult<JObject>> GetMember(string hid)
{
var member = await _repo.GetMemberByHid(hid);
if (member == null) return NotFound("Member not found.");
return Ok(member.ToJson(User.ContextFor(member), true));
}
[HttpPost]
[Authorize]
public async Task<ActionResult<JObject>> PostMember([FromBody] JObject properties)
{
if (!properties.ContainsKey("name"))
return BadRequest("Member name must be specified.");
var systemId = User.CurrentSystem();
var config = await _repo.GetSystemConfig(systemId);
await using var conn = await _db.Obtain();
// Enforce per-system member limit
var memberCount = await conn.QuerySingleAsync<int>("select count(*) from members where system = @System",
new { System = systemId });
var memberLimit = config.MemberLimitOverride ?? Limits.MaxMemberCount;
if (memberCount >= memberLimit)
return BadRequest($"Member limit reached ({memberCount} / {memberLimit}).");
await using var tx = await conn.BeginTransactionAsync();
var member = await _repo.CreateMember(systemId, properties.Value<string>("name"), conn);
var patch = MemberPatch.FromJSON(properties);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
{
await tx.RollbackAsync();
var err = patch.Errors[0];
if (err is FieldTooLongError)
return BadRequest($"Field {err.Key} is too long "
+ $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength}).");
if (err.Text != null)
return BadRequest(err.Text);
return BadRequest($"Field {err.Key} is invalid.");
}
member = await _repo.UpdateMember(member.Id, patch, conn);
await tx.CommitAsync();
return Ok(member.ToJson(User.ContextFor(member), true));
}
[HttpPatch("{hid}")]
[Authorize]
public async Task<ActionResult<JObject>> PatchMember(string hid, [FromBody] JObject changes)
{
var member = await _repo.GetMemberByHid(hid);
if (member == null) return NotFound("Member not found.");
var res = await _auth.AuthorizeAsync(User, member, "EditMember");
if (!res.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, $"Member '{hid}' is not part of your system.");
var patch = MemberPatch.FromJSON(changes);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
{
var err = patch.Errors[0];
if (err is FieldTooLongError)
return BadRequest($"Field {err.Key} is too long "
+ $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength}).");
if (err.Text != null)
return BadRequest(err.Text);
return BadRequest($"Field {err.Key} is invalid.");
}
var newMember = await _repo.UpdateMember(member.Id, patch);
return Ok(newMember.ToJson(User.ContextFor(newMember), true));
}
[HttpDelete("{hid}")]
[Authorize]
public async Task<ActionResult> DeleteMember(string hid)
{
var member = await _repo.GetMemberByHid(hid);
if (member == null) return NotFound("Member not found.");
var res = await _auth.AuthorizeAsync(User, member, "EditMember");
if (!res.Succeeded) return StatusCode(StatusCodes.Status403Forbidden, $"Member '{hid}' is not part of your system.");
await _repo.DeleteMember(member.Id);
return Ok();
}
}

View File

@@ -1,31 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq;
using PluralKit.Core;
namespace PluralKit.API;
[ApiController]
[Route("v1")]
public class MessageController: ControllerBase
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
public MessageController(ModelRepository repo, IDatabase db)
{
_repo = repo;
_db = db;
}
[HttpGet("msg/{mid}")]
public async Task<ActionResult<JObject>> GetMessage(ulong mid)
{
var msg = await _db.Execute(c => _repo.GetMessage(c, mid));
if (msg == null) return NotFound("Message not found.");
var ctx = msg.System == null ? LookupContext.ByNonOwner : User.ContextFor(msg.System);
return msg.ToJson(ctx, APIVersion.V1);
}
}

View File

@@ -1,197 +0,0 @@
using Dapper;
using Microsoft.AspNetCore.Authorization;
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 Instant? Timestamp { get; set; }
public ICollection<string> Members { get; set; }
}
[ApiController]
[Route("v1/s")]
public class SystemController: ControllerBase
{
private readonly IDatabase _db;
private readonly ModelRepository _repo;
private readonly IAuthorizationService _auth;
public SystemController(IDatabase db, IAuthorizationService auth, ModelRepository repo)
{
_db = db;
_auth = auth;
_repo = repo;
}
[HttpGet]
[Authorize]
public async Task<ActionResult<JObject>> GetOwnSystem()
{
var system = await _repo.GetSystem(User.CurrentSystem());
return system.ToJson(User.ContextFor(system));
}
[HttpGet("{hid}")]
public async Task<ActionResult<JObject>> GetSystem(string hid)
{
var system = await _repo.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 _repo.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 = _repo.GetSystemMembers(system.Id);
return Ok(await members
.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)
{
if (before == null) before = SystemClock.Instance.GetCurrentInstant();
var system = await _repo.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.");
var res = await _db.Execute(conn => 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 _repo.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 _repo.GetLatestSwitch(system.Id);
if (sw == null) return NotFound("System has no registered switches.");
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
return Ok(new FrontersReturn
{
Timestamp = sw.Timestamp,
Members = await members.Select(m => m.ToJson(User.ContextFor(system), true)).ToListAsync()
});
}
[HttpPatch]
[Authorize]
public async Task<ActionResult<JObject>> EditSystem([FromBody] JObject changes)
{
var system = await _repo.GetSystem(User.CurrentSystem());
var patch = SystemPatch.FromJSON(changes);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
{
var err = patch.Errors[0];
if (err is FieldTooLongError)
return BadRequest($"Field {err.Key} is too long "
+ $"({(err as FieldTooLongError).ActualLength} > {(err as FieldTooLongError).MaxLength}).");
return BadRequest($"Field {err.Key} is invalid.");
}
system = await _repo.UpdateSystem(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.");
await using var conn = await _db.Obtain();
// We get the current switch, if it exists
var latestSwitch = await _repo.GetLatestSwitch(User.CurrentSystem());
if (latestSwitch != null)
{
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
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);
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 _repo.AddSwitch(conn, User.CurrentSystem(), membersInOrder.Select(m => m.Id).ToList());
return NoContent();
}
}

View File

@@ -97,6 +97,6 @@ public class DiscordControllerV2: PKControllerBase
throw Errors.MessageNotFound;
var ctx = msg.System == null ? LookupContext.ByNonOwner : ContextFor(msg.System);
return msg.ToJson(ctx, APIVersion.V2);
return msg.ToJson(ctx);
}
}

View File

@@ -24,7 +24,7 @@ public class GroupControllerV2: PKControllerBase
if (with_members && !system.MemberListPrivacy.CanAccess(ctx))
throw Errors.UnauthorizedMemberList;
if (!system.GroupListPrivacy.CanAccess(User.ContextFor(system)))
if (!system.GroupListPrivacy.CanAccess(ContextFor(system)))
throw Errors.UnauthorizedGroupList;
var groups = _repo.GetSystemGroups(system.Id);

View File

@@ -29,7 +29,7 @@ public class GroupMemberControllerV2: PKControllerBase
var o = new JArray();
await foreach (var member in members)
o.Add(member.ToJson(ctx, v: APIVersion.V2));
o.Add(member.ToJson(ctx));
return Ok(o);
}

View File

@@ -28,7 +28,7 @@ public class MemberControllerV2: PKControllerBase
var members = _repo.GetSystemMembers(system.Id);
return Ok(await members
.Where(m => m.MemberVisibility.CanAccess(ctx))
.Select(m => m.ToJson(ctx, v: APIVersion.V2))
.Select(m => m.ToJson(ctx))
.ToListAsync());
}
@@ -43,7 +43,7 @@ public class MemberControllerV2: PKControllerBase
if (memberCount >= memberLimit)
throw Errors.MemberLimitReached;
var patch = MemberPatch.FromJSON(data, APIVersion.V2);
var patch = MemberPatch.FromJSON(data);
patch.AssertIsValid();
if (!patch.Name.IsPresent)
patch.Errors.Add(new ValidationError("name", "Key 'name' is required when creating new member."));
@@ -64,7 +64,7 @@ public class MemberControllerV2: PKControllerBase
await tx.CommitAsync();
return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
return Ok(newMember.ToJson(LookupContext.ByOwner));
}
[HttpGet("members/{memberRef}")]
@@ -76,7 +76,7 @@ public class MemberControllerV2: PKControllerBase
var system = await _repo.GetSystem(member.System);
return Ok(member.ToJson(ContextFor(member), systemStr: system.Hid, v: APIVersion.V2));
return Ok(member.ToJson(ContextFor(member), systemStr: system.Hid));
}
[HttpPatch("members/{memberRef}")]
@@ -89,14 +89,14 @@ public class MemberControllerV2: PKControllerBase
if (member.System != system.Id)
throw Errors.NotOwnMemberError;
var patch = MemberPatch.FromJSON(data, APIVersion.V2);
var patch = MemberPatch.FromJSON(data);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
throw new ModelParseError(patch.Errors);
var newMember = await _repo.UpdateMember(member.Id, patch);
return Ok(newMember.ToJson(LookupContext.ByOwner, v: APIVersion.V2));
return Ok(newMember.ToJson(LookupContext.ByOwner));
}
[HttpDelete("members/{memberRef}")]

View File

@@ -70,7 +70,7 @@ public class SwitchControllerV2: PKControllerBase
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)).ToListAsync(),
Uuid = sw.Uuid,
});
}
@@ -124,7 +124,7 @@ public class SwitchControllerV2: PKControllerBase
{
Uuid = newSwitch.Uuid,
Timestamp = data.Timestamp != null ? data.Timestamp.Value : newSwitch.Timestamp,
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2)),
Members = members.Select(x => x.ToJson(LookupContext.ByOwner)),
});
}
@@ -153,7 +153,7 @@ public class SwitchControllerV2: PKControllerBase
{
Uuid = sw.Uuid,
Timestamp = sw.Timestamp,
Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
Members = await members.Select(m => m.ToJson(ctx)).ToListAsync()
});
}
@@ -190,7 +190,7 @@ public class SwitchControllerV2: PKControllerBase
{
Uuid = sw.Uuid,
Timestamp = sw.Timestamp,
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2))
Members = members.Select(x => x.ToJson(LookupContext.ByOwner))
});
}
@@ -238,7 +238,7 @@ public class SwitchControllerV2: PKControllerBase
{
Uuid = sw.Uuid,
Timestamp = sw.Timestamp,
Members = members.Select(x => x.ToJson(LookupContext.ByOwner, v: APIVersion.V2))
Members = members.Select(x => x.ToJson(LookupContext.ByOwner))
});
}
@@ -261,4 +261,10 @@ public class SwitchControllerV2: PKControllerBase
return NoContent();
}
}
public struct PostSwitchParams
{
public Instant? Timestamp { get; set; }
public ICollection<string> Members { get; set; }
}

View File

@@ -17,7 +17,7 @@ public class SystemControllerV2: PKControllerBase
{
var system = await ResolveSystem(systemRef);
if (system == null) throw Errors.SystemNotFound;
return Ok(system.ToJson(ContextFor(system), APIVersion.V2));
return Ok(system.ToJson(ContextFor(system)));
}
[HttpPatch("{systemRef}")]
@@ -27,14 +27,14 @@ public class SystemControllerV2: PKControllerBase
if (system == null) throw Errors.SystemNotFound;
if (ContextFor(system) != LookupContext.ByOwner)
throw Errors.GenericMissingPermissions;
var patch = SystemPatch.FromJSON(data, APIVersion.V2);
var patch = SystemPatch.FromJSON(data);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
throw new ModelParseError(patch.Errors);
var newSystem = await _repo.UpdateSystem(system.Id, patch);
return Ok(newSystem.ToJson(LookupContext.ByOwner, APIVersion.V2));
return Ok(newSystem.ToJson(LookupContext.ByOwner));
}
[HttpGet("{systemRef}/settings")]