feat(apiv2): GET endpoints except guilds

- ResolveT methods in ControllerBase
- ContextFor methods in ControllerBase
This commit is contained in:
spiral 2021-10-12 05:17:54 -04:00
parent 11620d94c8
commit e2a56a198f
No known key found for this signature in database
GPG Key ID: A6059F0CA0E1BD31
9 changed files with 249 additions and 60 deletions

View File

@ -35,7 +35,8 @@ namespace PluralKit.API
if (systemRef == "@me") if (systemRef == "@me")
{ {
HttpContext.Items.TryGetValue("SystemId", out var systemId); HttpContext.Items.TryGetValue("SystemId", out var systemId);
if (systemId == null) return null; if (systemId == null)
throw APIErrors.GenericAuthError;
return _repo.GetSystem((SystemId)systemId); return _repo.GetSystem((SystemId)systemId);
} }
@ -51,11 +52,47 @@ namespace PluralKit.API
return null; return null;
} }
public LookupContext LookupContextFor(PKSystem target) protected Task<PKMember?> ResolveMember(string memberRef)
{
if (Guid.TryParse(memberRef, out var guid))
return _repo.GetMemberByGuid(guid);
if (_shortIdRegex.IsMatch(memberRef))
return _repo.GetMemberByHid(memberRef);
return null;
}
protected Task<PKGroup?> ResolveGroup(string groupRef)
{
if (Guid.TryParse(groupRef, out var guid))
return _repo.GetGroupByGuid(guid);
if (_shortIdRegex.IsMatch(groupRef))
return _repo.GetGroupByHid(groupRef);
return null;
}
public LookupContext ContextFor(PKSystem system)
{ {
HttpContext.Items.TryGetValue("SystemId", out var systemId); HttpContext.Items.TryGetValue("SystemId", out var systemId);
if (systemId == null) return LookupContext.ByNonOwner; if (systemId == null) return LookupContext.ByNonOwner;
return target.Id == (SystemId)systemId ? LookupContext.ByOwner : LookupContext.ByNonOwner; return ((SystemId)systemId) == system.Id ? LookupContext.ByOwner : LookupContext.ByNonOwner;
}
public LookupContext ContextFor(PKMember member)
{
HttpContext.Items.TryGetValue("SystemId", out var systemId);
if (systemId == null) return LookupContext.ByNonOwner;
return ((SystemId)systemId) == member.System ? LookupContext.ByOwner : LookupContext.ByNonOwner;
}
public LookupContext ContextFor(PKGroup group)
{
HttpContext.Items.TryGetValue("SystemId", out var systemId);
if (systemId == null) return LookupContext.ByNonOwner;
return ((SystemId)systemId) == group.System ? LookupContext.ByOwner : LookupContext.ByNonOwner;
} }
} }
} }

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -16,13 +17,23 @@ namespace PluralKit.API
{ {
public GroupControllerV2(IServiceProvider svc) : base(svc) { } public GroupControllerV2(IServiceProvider svc) : base(svc) { }
[HttpGet("systems/{system_id}/groups")] [HttpGet("systems/{systemRef}/groups")]
public async Task<IActionResult> GetSystemGroups(string system_id) public async Task<IActionResult> GetSystemGroups(string systemRef)
{ {
return new ObjectResult("Unimplemented") var system = await ResolveSystem(systemRef);
{ if (system == null)
StatusCode = 501 throw APIErrors.SystemNotFound;
};
var ctx = this.ContextFor(system);
if (!system.GroupListPrivacy.CanAccess(User.ContextFor(system)))
throw APIErrors.UnauthorizedGroupList;
var groups = _repo.GetSystemGroups(system.Id);
return Ok(await groups
.Where(g => g.Visibility.CanAccess(ctx))
.Select(g => g.ToJson(ctx))
.ToListAsync());
} }
[HttpPost("groups")] [HttpPost("groups")]
@ -34,13 +45,16 @@ namespace PluralKit.API
}; };
} }
[HttpGet("groups/{group_id}")] [HttpGet("groups/{groupRef}")]
public async Task<IActionResult> GroupGet(string group_id) public async Task<IActionResult> GroupGet(string groupRef)
{ {
return new ObjectResult("Unimplemented") var group = await ResolveGroup(groupRef);
{ if (group == null)
StatusCode = 501 throw APIErrors.GroupNotFound;
};
var system = await _repo.GetSystem(group.System);
return Ok(group.ToJson(this.ContextFor(group), systemStr: system.Hid));
} }
[HttpPatch("groups/{group_id}")] [HttpPatch("groups/{group_id}")]

View File

@ -1,10 +1,13 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using PluralKit.Core;
namespace PluralKit.API namespace PluralKit.API
{ {
[ApiController] [ApiController]
@ -14,22 +17,46 @@ namespace PluralKit.API
{ {
public GroupMemberControllerV2(IServiceProvider svc) : base(svc) { } public GroupMemberControllerV2(IServiceProvider svc) : base(svc) { }
[HttpGet("groups/{group_id}/members")] [HttpGet("groups/{groupRef}/members")]
public async Task<IActionResult> GetGroupMembers(string group_id) public async Task<IActionResult> GetGroupMembers(string groupRef)
{ {
return new ObjectResult("Unimplemented") var group = await ResolveGroup(groupRef);
{ if (group == null)
StatusCode = 501 throw APIErrors.GroupNotFound;
};
var ctx = this.ContextFor(group);
if (!group.ListPrivacy.CanAccess(ctx))
throw APIErrors.UnauthorizedGroupMemberList;
var members = _repo.GetGroupMembers(group.Id).Where(m => m.MemberVisibility.CanAccess(ctx));
var o = new JArray();
await foreach (var member in members)
o.Add(member.ToJson(ctx, v: APIVersion.V2));
return Ok(o);
} }
[HttpGet("members/{member_id}/groups")] [HttpGet("members/{memberRef}/groups")]
public async Task<IActionResult> GetMemberGroups(string member_id) public async Task<IActionResult> GetMemberGroups(string memberRef)
{ {
return new ObjectResult("Unimplemented") var member = await ResolveMember(memberRef);
{ var ctx = this.ContextFor(member);
StatusCode = 501
}; var system = await _repo.GetSystem(member.System);
if (!system.GroupListPrivacy.CanAccess(ctx))
throw APIErrors.UnauthorizedGroupList;
var groups = _repo.GetMemberGroups(member.Id).Where(g => g.Visibility.CanAccess(ctx));
var o = new JArray();
await foreach (var group in groups)
o.Add(group.ToJson(ctx));
return Ok(o);
} }
[HttpPut("groups/{group_id}/members/{member_id}")] [HttpPut("groups/{group_id}/members/{member_id}")]

View File

@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
@ -17,13 +18,23 @@ namespace PluralKit.API
public MemberControllerV2(IServiceProvider svc) : base(svc) { } public MemberControllerV2(IServiceProvider svc) : base(svc) { }
[HttpGet("systems/{system}/members")] [HttpGet("systems/{systemRef}/members")]
public async Task<IActionResult> GetSystemMembers(string system) public async Task<IActionResult> GetSystemMembers(string systemRef)
{ {
return new ObjectResult("Unimplemented") var system = await ResolveSystem(systemRef);
{ if (system == null)
StatusCode = 501 throw APIErrors.SystemNotFound;
};
var ctx = this.ContextFor(system);
if (!system.MemberListPrivacy.CanAccess(this.ContextFor(system)))
throw APIErrors.UnauthorizedMemberList;
var members = _repo.GetSystemMembers(system.Id);
return Ok(await members
.Where(m => m.MemberVisibility.CanAccess(ctx))
.Select(m => m.ToJson(ctx, v: APIVersion.V2))
.ToListAsync());
} }
[HttpPost("members")] [HttpPost("members")]
@ -35,13 +46,16 @@ namespace PluralKit.API
}; };
} }
[HttpGet("members/{member}")] [HttpGet("members/{memberRef}")]
public async Task<IActionResult> MemberGet(string member) public async Task<IActionResult> MemberGet(string memberRef)
{ {
return new ObjectResult("Unimplemented") var member = await ResolveMember(memberRef);
{ if (member == null)
StatusCode = 501 throw APIErrors.MemberNotFound;
};
var system = await _repo.GetSystem(member.System);
return Ok(member.ToJson(this.ContextFor(member), systemStr: system.Hid, v: APIVersion.V2));
} }
[HttpPatch("members/{member}")] [HttpPatch("members/{member}")]

View File

@ -5,6 +5,8 @@ using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.API namespace PluralKit.API
@ -28,12 +30,25 @@ namespace PluralKit.API
return Ok(o); return Ok(o);
} }
[HttpGet("messages/{message_id}")] [HttpGet("messages/{messageId}")]
public async Task<IActionResult> MessageGet(ulong message_id) public async Task<ActionResult<MessageReturn>> MessageGet(ulong messageId)
{ {
return new ObjectResult("Unimplemented") var msg = await _db.Execute(c => _repo.GetMessage(c, messageId));
if (msg == null)
throw APIErrors.MessageNotFound;
var ctx = this.ContextFor(msg.System);
// todo: don't rely on v1 stuff
return new MessageReturn
{ {
StatusCode = 501 Timestamp = Instant.FromUnixTimeMilliseconds((long)(msg.Message.Mid >> 22) + 1420070400000),
Id = msg.Message.Mid.ToString(),
Channel = msg.Message.Channel.ToString(),
Sender = msg.Message.Sender.ToString(),
System = msg.System.ToJson(ctx, v: APIVersion.V2),
Member = msg.Member.ToJson(ctx, v: APIVersion.V2),
Original = msg.Message.OriginalMid?.ToString()
}; };
} }
} }

View File

@ -1,14 +1,28 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Dapper;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using NodaTime;
using PluralKit.Core; using PluralKit.Core;
namespace PluralKit.API namespace PluralKit.API
{ {
public struct SwitchesReturnNew
{
[JsonProperty("timestamp")] public Instant Timestamp { get; set; }
[JsonProperty("id")] public Guid Uuid { get; set; }
[JsonProperty("members")] public IEnumerable<string> Members { get; set; }
}
[ApiController] [ApiController]
[ApiVersion("2.0")] [ApiVersion("2.0")]
[Route("v{version:apiVersion}")] [Route("v{version:apiVersion}")]
@ -17,22 +31,57 @@ namespace PluralKit.API
public SwitchControllerV2(IServiceProvider svc) : base(svc) { } public SwitchControllerV2(IServiceProvider svc) : base(svc) { }
[HttpGet("systems/{system}/switches")] [HttpGet("systems/{systemRef}/switches")]
public async Task<IActionResult> GetSystemSwitches(string system) public async Task<IActionResult> GetSystemSwitches(string systemRef, [FromQuery(Name = "before")] Instant? before, [FromQuery(Name = "limit")] int? limit)
{ {
return new ObjectResult("Unimplemented") var system = await ResolveSystem(systemRef);
{ if (system == null)
StatusCode = 501 throw APIErrors.SystemNotFound;
};
var ctx = this.ContextFor(system);
if (!system.FrontHistoryPrivacy.CanAccess(ctx))
throw APIErrors.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/{system}/fronters")] [HttpGet("systems/{systemRef}/fronters")]
public async Task<IActionResult> GetSystemFronters(string system) public async Task<IActionResult> GetSystemFronters(string systemRef)
{ {
return new ObjectResult("Unimplemented") var system = await ResolveSystem(systemRef);
if (system == null)
throw APIErrors.SystemNotFound;
var ctx = this.ContextFor(system);
if (!system.FrontPrivacy.CanAccess(ctx))
throw APIErrors.UnauthorizedCurrentFronters;
var sw = await _repo.GetLatestSwitch(system.Id);
if (sw == null)
return NoContent();
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
return Ok(new FrontersReturn
{ {
StatusCode = 501 Timestamp = sw.Timestamp,
}; Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
});
} }
@ -46,13 +95,31 @@ namespace PluralKit.API
} }
[HttpGet("systems/{system}/switches/{switch_id}")] [HttpGet("systems/{systemRef}/switches/{switchRef}")]
public async Task<IActionResult> SwitchGet(string system, string switch_id) public async Task<IActionResult> SwitchGet(string systemRef, string switchRef)
{ {
return new ObjectResult("Unimplemented") if (!Guid.TryParse(switchRef, out var switchId))
throw APIErrors.SwitchNotFound;
var system = await ResolveSystem(systemRef);
if (system == null)
throw APIErrors.SystemNotFound;
var ctx = this.ContextFor(system);
if (!system.FrontHistoryPrivacy.CanAccess(ctx))
throw APIErrors.UnauthorizedFrontHistory;
var sw = await _repo.GetSwitchByUuid(switchId);
if (sw == null)
throw APIErrors.SwitchNotFound;
var members = _db.Execute(conn => _repo.GetSwitchMembers(conn, sw.Id));
return Ok(new FrontersReturn
{ {
StatusCode = 501 Timestamp = sw.Timestamp,
}; Members = await members.Select(m => m.ToJson(ctx, v: APIVersion.V2)).ToListAsync()
});
} }
[HttpPatch("systems/{system}/switches/{switch_id}")] [HttpPatch("systems/{system}/switches/{switch_id}")]

View File

@ -21,7 +21,7 @@ namespace PluralKit.API
{ {
var system = await ResolveSystem(systemRef); var system = await ResolveSystem(systemRef);
if (system == null) return NotFound(); if (system == null) return NotFound();
else return Ok(system.ToJson(LookupContextFor(system))); else return Ok(system.ToJson(this.ContextFor(system), v: APIVersion.V2));
} }
[HttpPatch("{system}")] [HttpPatch("{system}")]

View File

@ -41,11 +41,17 @@ namespace PluralKit.API
public static class APIErrors public static class APIErrors
{ {
public static PKError GenericBadRequest = new(400, 0, "400: Bad Request"); public static PKError GenericBadRequest = new(400, 0, "400: Bad Request");
public static PKError GenericAuthError = new(401, 0, "401: Missing or invalid Authorization header");
public static PKError SystemNotFound = new(404, 20001, "System not found."); public static PKError SystemNotFound = new(404, 20001, "System not found.");
public static PKError MemberNotFound = new(404, 20002, "Member not found."); public static PKError MemberNotFound = new(404, 20002, "Member not found.");
public static PKError GroupNotFound = new(404, 20003, "Group not found."); public static PKError GroupNotFound = new(404, 20003, "Group not found.");
public static PKError MessageNotFound = new(404, 20004, "Message not found.");
public static PKError SwitchNotFound = new(404, 20005, "Switch not found.");
public static PKError UnauthorizedMemberList = new(403, 30001, "Unauthorized to view member list"); public static PKError UnauthorizedMemberList = new(403, 30001, "Unauthorized to view member list");
public static PKError UnauthorizedGroupList = new(403, 30002, "Unauthorized to view group list"); public static PKError UnauthorizedGroupList = new(403, 30002, "Unauthorized to view group list");
public static PKError UnauthorizedGroupMemberList = new(403, 30003, "Unauthorized to view group member list");
public static PKError UnauthorizedCurrentFronters = new(403, 30004, "Unauthorized to view current fronters.");
public static PKError UnauthorizedFrontHistory = new(403, 30004, "Unauthorized to view front history.");
public static PKError Unimplemented = new(501, 50001, "Unimplemented"); public static PKError Unimplemented = new(501, 50001, "Unimplemented");
} }
} }

View File

@ -16,6 +16,15 @@ namespace PluralKit.Core
return _db.QueryStream<PKGroup>(query); return _db.QueryStream<PKGroup>(query);
} }
public IAsyncEnumerable<PKMember> GetGroupMembers(GroupId id)
{
var query = new Query("group_members")
.Select("members.*")
.Join("members", "group_members.member_id", "members.id")
.Where("group_members.group_id", id);
return _db.QueryStream<PKMember>(query);
}
// todo: add this to metrics tracking // todo: add this to metrics tracking
public async Task AddGroupsToMember(MemberId member, IReadOnlyCollection<GroupId> groups) public async Task AddGroupsToMember(MemberId member, IReadOnlyCollection<GroupId> groups)
{ {