Add basic API, only with system endpoints
This commit is contained in:
156
PluralKit.API/Controllers/SystemController.cs
Normal file
156
PluralKit.API/Controllers/SystemController.cs
Normal file
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using NodaTime;
|
||||
|
||||
namespace PluralKit.API.Controllers
|
||||
{
|
||||
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<PKMember> Members { get; set; }
|
||||
}
|
||||
|
||||
public struct PostSwitchParams
|
||||
{
|
||||
public ICollection<string> Members { get; set; }
|
||||
}
|
||||
|
||||
[ApiController]
|
||||
[Route("s")]
|
||||
public class SystemController : ControllerBase
|
||||
{
|
||||
private SystemStore _systems;
|
||||
private MemberStore _members;
|
||||
private SwitchStore _switches;
|
||||
private IDbConnection _conn;
|
||||
private TokenAuthService _auth;
|
||||
|
||||
public SystemController(SystemStore systems, MemberStore members, SwitchStore switches, IDbConnection conn, TokenAuthService auth)
|
||||
{
|
||||
_systems = systems;
|
||||
_members = members;
|
||||
_switches = switches;
|
||||
_conn = conn;
|
||||
_auth = auth;
|
||||
}
|
||||
|
||||
[HttpGet("{hid}")]
|
||||
public async Task<ActionResult<PKSystem>> GetSystem(string hid)
|
||||
{
|
||||
var system = await _systems.GetByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
return Ok(system);
|
||||
}
|
||||
|
||||
[HttpGet("{hid}/members")]
|
||||
public async Task<ActionResult<IEnumerable<PKMember>>> GetMembers(string hid)
|
||||
{
|
||||
var system = await _systems.GetByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
|
||||
var members = await _members.GetBySystem(system);
|
||||
return Ok(members);
|
||||
}
|
||||
|
||||
[HttpGet("{hid}/switches")]
|
||||
public async Task<ActionResult<IEnumerable<SwitchesReturn>>> GetSwitches(string hid, [FromQuery(Name = "before")] Instant? before)
|
||||
{
|
||||
if (before == default(Instant)) before = SystemClock.Instance.GetCurrentInstant();
|
||||
|
||||
var system = await _systems.GetByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
|
||||
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 _systems.GetByHid(hid);
|
||||
if (system == null) return NotFound("System not found.");
|
||||
|
||||
var sw = await _switches.GetLatestSwitch(system);
|
||||
var members = await _switches.GetSwitchMembers(sw);
|
||||
return Ok(new FrontersReturn
|
||||
{
|
||||
Timestamp = sw.Timestamp,
|
||||
Members = members
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPatch]
|
||||
public async Task<ActionResult<PKSystem>> EditSystem([FromBody] PKSystem newSystem)
|
||||
{
|
||||
if (_auth.CurrentSystem == null) return Unauthorized("No token specified in Authorization header.");
|
||||
var system = _auth.CurrentSystem;
|
||||
|
||||
system.Name = newSystem.Name;
|
||||
system.Description = newSystem.Description;
|
||||
system.Tag = newSystem.Tag;
|
||||
system.AvatarUrl = newSystem.AvatarUrl;
|
||||
system.UiTz = newSystem.UiTz ?? "UTC";
|
||||
|
||||
await _systems.Save(system);
|
||||
return Ok(system);
|
||||
}
|
||||
|
||||
[HttpPost("switches")]
|
||||
public async Task<IActionResult> PostSwitch([FromBody] PostSwitchParams param)
|
||||
{
|
||||
if (_auth.CurrentSystem == null) return Unauthorized("No token specified in Authorization header.");
|
||||
|
||||
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 _switches.GetLatestSwitch(_auth.CurrentSystem);
|
||||
var latestSwitchMembers = await _switches.GetSwitchMembers(latestSwitch);
|
||||
|
||||
// Bail if this switch is identical to the latest one
|
||||
if (latestSwitchMembers.Select(m => m.Hid).SequenceEqual(param.Members))
|
||||
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 != _auth.CurrentSystem.Id)
|
||||
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 _switches.RegisterSwitch(_auth.CurrentSystem, membersInOrder);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
PluralKit.API/PluralKit.API.csproj
Normal file
17
PluralKit.API/PluralKit.API.csproj
Normal file
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netcoreapp2.2</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.HttpsPolicy" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\PluralKit.Core\PluralKit.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
26
PluralKit.API/Program.cs
Normal file
26
PluralKit.API/Program.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
InitUtils.Init();
|
||||
CreateWebHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
|
||||
WebHost.CreateDefaultBuilder(args)
|
||||
.UseConfiguration(InitUtils.BuildConfiguration(args).Build())
|
||||
.UseStartup<Startup>();
|
||||
}
|
||||
}
|
||||
30
PluralKit.API/Properties/launchSettings.json
Normal file
30
PluralKit.API/Properties/launchSettings.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||
"iisSettings": {
|
||||
"windowsAuthentication": false,
|
||||
"anonymousAuthentication": true,
|
||||
"iisExpress": {
|
||||
"applicationUrl": "http://localhost:48228",
|
||||
"sslPort": 44372
|
||||
}
|
||||
},
|
||||
"profiles": {
|
||||
"IIS Express": {
|
||||
"commandName": "IISExpress",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"PluralKit.API": {
|
||||
"commandName": "Project",
|
||||
"launchBrowser": true,
|
||||
"launchUrl": "api/values",
|
||||
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
72
PluralKit.API/Startup.cs
Normal file
72
PluralKit.API/Startup.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Builder;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.HttpsPolicy;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using NodaTime;
|
||||
using NodaTime.Serialization.JsonNet;
|
||||
using Npgsql;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public class Startup
|
||||
{
|
||||
public Startup(IConfiguration configuration)
|
||||
{
|
||||
Configuration = configuration;
|
||||
}
|
||||
|
||||
public IConfiguration Configuration { get; }
|
||||
|
||||
// This method gets called by the runtime. Use this method to add services to the container.
|
||||
public void ConfigureServices(IServiceCollection services)
|
||||
{
|
||||
services.AddMvc(opts => { })
|
||||
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
|
||||
.AddJsonOptions(opts => { opts.SerializerSettings.BuildSerializerSettings(); });
|
||||
|
||||
services
|
||||
.AddTransient<SystemStore>()
|
||||
.AddTransient<MemberStore>()
|
||||
.AddTransient<SwitchStore>()
|
||||
.AddTransient<MessageStore>()
|
||||
|
||||
.AddScoped<TokenAuthService>()
|
||||
|
||||
.AddTransient(_ => Configuration.GetSection("PluralKit").Get<CoreConfig>() ?? new CoreConfig())
|
||||
.AddScoped<IDbConnection>(svc =>
|
||||
{
|
||||
var conn = new NpgsqlConnection(svc.GetRequiredService<CoreConfig>().Database);
|
||||
conn.Open();
|
||||
return conn;
|
||||
});
|
||||
}
|
||||
|
||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
|
||||
{
|
||||
if (env.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
|
||||
//app.UseHsts();
|
||||
}
|
||||
|
||||
app.UseHttpsRedirection();
|
||||
app.UseMiddleware<TokenAuthService>();
|
||||
app.UseMvc();
|
||||
}
|
||||
}
|
||||
}
|
||||
31
PluralKit.API/TokenAuthService.cs
Normal file
31
PluralKit.API/TokenAuthService.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace PluralKit.API
|
||||
{
|
||||
public class TokenAuthService: IMiddleware
|
||||
{
|
||||
public PKSystem CurrentSystem { get; set; }
|
||||
|
||||
private SystemStore _systems;
|
||||
|
||||
public TokenAuthService(SystemStore systems)
|
||||
{
|
||||
_systems = systems;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
|
||||
{
|
||||
var token = context.Request.Headers["Authorization"].FirstOrDefault();
|
||||
if (token != null)
|
||||
{
|
||||
CurrentSystem = await _systems.GetByToken(token);
|
||||
}
|
||||
|
||||
await next.Invoke(context);
|
||||
CurrentSystem = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
6
PluralKit.API/app.config
Normal file
6
PluralKit.API/app.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<gcServer enabled="true"/>
|
||||
</runtime>
|
||||
</configuration>
|
||||
9
PluralKit.API/appsettings.Development.json
Normal file
9
PluralKit.API/appsettings.Development.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Debug",
|
||||
"System": "Information",
|
||||
"Microsoft": "Information"
|
||||
}
|
||||
}
|
||||
}
|
||||
8
PluralKit.API/appsettings.json
Normal file
8
PluralKit.API/appsettings.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
Reference in New Issue
Block a user