Add swagger and docs

This commit is contained in:
Kieran 2022-03-08 13:47:42 +00:00
parent 22877e214b
commit d7d092bc63
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
14 changed files with 194 additions and 7 deletions

3
.gitignore vendored
View File

@ -15,4 +15,5 @@ out/
sw.js
.DS_Store
.idea/
appsettings.*.json
appsettings.*.json
VoidCat.xml

View File

@ -20,6 +20,11 @@ public class AdminController : Controller
_fileInfo = fileInfo;
}
/// <summary>
/// List all files in the system
/// </summary>
/// <param name="request">Page request</param>
/// <returns></returns>
[HttpPost]
[Route("file")]
public async Task<RenderedResults<PublicVoidFile>> ListFiles([FromBody] PagedRequest request)
@ -27,6 +32,10 @@ public class AdminController : Controller
return await (await _fileStore.ListFiles(request)).GetResults();
}
/// <summary>
/// Delete a file from the system
/// </summary>
/// <param name="id">Id of the file to delete</param>
[HttpDelete]
[Route("file/{id}")]
public async Task DeleteFile([FromRoute] string id)
@ -36,6 +45,11 @@ public class AdminController : Controller
await _fileInfo.Delete(gid);
}
/// <summary>
/// List all users in the system
/// </summary>
/// <param name="request">Page request</param>
/// <returns></returns>
[HttpPost]
[Route("user")]
public async Task<RenderedResults<PrivateVoidUser>> ListUsers([FromBody] PagedRequest request)

View File

@ -22,6 +22,11 @@ public class AuthController : Controller
_settings = settings;
}
/// <summary>
/// Login to a user account
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
[Route("login")]
public async Task<LoginResponse> Login([FromBody] LoginRequest req)
@ -45,6 +50,11 @@ public class AuthController : Controller
}
}
/// <summary>
/// Register a new account
/// </summary>
/// <param name="req"></param>
/// <returns></returns>
[HttpPost]
[Route("register")]
public async Task<LoginResponse> Register([FromBody] LoginRequest req)

View File

@ -31,6 +31,10 @@ public class DownloadController : Controller
return SetupDownload(gid);
}
/// <summary>
/// Download a specific file by Id
/// </summary>
/// <param name="id"></param>
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 86400)]
[HttpGet]
[Route("{id}")]

View File

@ -16,6 +16,11 @@ namespace VoidCat.Controllers
_fileStore = fileStore;
}
/// <summary>
/// Return system info
/// </summary>
/// <returns></returns>
[HttpGet]
[ResponseCache(Location = ResponseCacheLocation.Any, Duration = 60)]
public async Task<GlobalStats> GetGlobalStats()
@ -33,6 +38,11 @@ namespace VoidCat.Controllers
return new(bw, bytes, count, BuildInfo.GetBuildInfo());
}
/// <summary>
/// Get stats for a specific file
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
[Route("{id}")]
public async Task<FileStats> GetFileStats([FromRoute] string id)

View File

@ -27,6 +27,20 @@ namespace VoidCat.Controllers
_fileInfo = fileInfo;
}
/// <summary>
/// Primary upload endpoint
/// </summary>
/// <remarks>
/// Additional optional headers can be included to provide details about the file being uploaded:
///
/// `V-Content-Type` - Sets the `mimeType` of the file and is used on the preview page to display the file.
/// `V-Filename` - Sets the filename of the file.
/// `V-Description` - Sets the description of the file.
/// `V-Full-Digest` - Include a SHA256 hash of the entire file for verification purposes.
/// `V-Digest` - A SHA256 hash of the data you are sending in this request.
/// </remarks>
/// <param name="cli">True if you want to return only the url of the file in the response</param>
/// <returns>Returns <see cref="UploadResult"/></returns>
[HttpPost]
[DisableRequestSizeLimit]
[DisableFormValueModelBinding]
@ -67,6 +81,18 @@ namespace VoidCat.Controllers
}
}
/// <summary>
/// Append data onto a file
/// </summary>
/// <remarks>
/// This endpoint is mainly used to bypass file upload limits enforced by CloudFlare.
/// Clients should split their uploads into segments, upload the first segment to the regular
/// upload endpoint, use the `editSecret` to append data to the file.
///
/// Set the edit secret in the header `V-EditSecret` otherwise you will not be able to append data.
/// </remarks>
/// <param name="id"></param>
/// <returns></returns>
[HttpPost]
[DisableRequestSizeLimit]
[DisableFormValueModelBinding]
@ -97,6 +123,11 @@ namespace VoidCat.Controllers
}
}
/// <summary>
/// Return information about a specific file
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet]
[Route("{id}")]
public ValueTask<PublicVoidFile?> GetInfo([FromRoute] string id)
@ -104,6 +135,11 @@ namespace VoidCat.Controllers
return _fileInfo.Get(id.FromBase58Guid());
}
/// <summary>
/// Create a paywall order to pay
/// </summary>
/// <param name="id">File id</param>
/// <returns></returns>
[HttpGet]
[Route("{id}/paywall")]
public async ValueTask<PaywallOrder?> CreateOrder([FromRoute] string id)
@ -116,6 +152,12 @@ namespace VoidCat.Controllers
return await provider.CreateOrder(file!);
}
/// <summary>
/// Return the status of an order
/// </summary>
/// <param name="id">File id</param>
/// <param name="order">Order id</param>
/// <returns></returns>
[HttpGet]
[Route("{id}/paywall/{order:guid}")]
public async ValueTask<PaywallOrder?> GetOrderStatus([FromRoute] string id, [FromRoute] Guid order)
@ -127,6 +169,12 @@ namespace VoidCat.Controllers
return await provider.GetOrderStatus(order);
}
/// <summary>
/// Update the paywall config
/// </summary>
/// <param name="id">File id</param>
/// <param name="req">Requested config to set on the file</param>
/// <returns></returns>
[HttpPost]
[Route("{id}/paywall")]
public async Task<IActionResult> SetPaywallConfig([FromRoute] string id, [FromBody] SetPaywallConfigRequest req)

View File

@ -18,13 +18,23 @@ public class UserController : Controller
_emailVerification = emailVerification;
}
/// <summary>
/// Return user profile
/// </summary>
/// <remarks>
/// You do not need to be logged in to return a user profile if their profile is set to public.
///
/// You may also use `me` as the `id` to get the logged in users profile.
/// </remarks>
/// <param name="id">User id to load</param>
/// <returns></returns>
[HttpGet]
public async Task<IActionResult> GetUser([FromRoute] string id)
{
var loggedUser = HttpContext.GetUserId();
var isMe = id.Equals("me", StringComparison.InvariantCultureIgnoreCase);
if (isMe && !loggedUser.HasValue) return Unauthorized();
var requestedId = isMe ? loggedUser!.Value : id.FromBase58Guid();
if (loggedUser == requestedId)
{
@ -39,6 +49,13 @@ public class UserController : Controller
return Json(user);
}
/// <summary>
/// Update profile settings
/// </summary>
///
/// <param name="id">User id</param>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost]
public async Task<IActionResult> UpdateUser([FromRoute] string id, [FromBody] PublicVoidUser user)
{
@ -51,6 +68,16 @@ public class UserController : Controller
return Ok();
}
/// <summary>
/// Return a list of files which the user has uploaded
/// </summary>
/// <remarks>
/// This will return files if the profile has public uploads set on their profile.
/// Otherwise you can return your own uploaded files if you are logged in.
/// </remarks>
/// <param name="id">User id</param>
/// <param name="request">Page request</param>
/// <returns></returns>
[HttpPost]
[Route("files")]
public async Task<IActionResult> ListUserFiles([FromRoute] string id,
@ -71,6 +98,11 @@ public class UserController : Controller
return Json(await results.GetResults());
}
/// <summary>
/// Send a verification code for a specific user
/// </summary>
/// <param name="id">User id to send code for</param>
/// <returns></returns>
[HttpGet]
[Route("verify")]
public async Task<IActionResult> SendVerificationCode([FromRoute] string id)
@ -85,6 +117,12 @@ public class UserController : Controller
return Accepted();
}
/// <summary>
/// Confirm email verification code
/// </summary>
/// <param name="id">User id to verify</param>
/// <param name="code">Verification code to check</param>
/// <returns></returns>
[HttpPost]
[Route("verify")]
public async Task<IActionResult> VerifyCode([FromRoute] string id, [FromBody] string code)

View File

@ -1,5 +1,8 @@
namespace VoidCat.Model.Exceptions;
/// <summary>
/// Specified file was not found
/// </summary>
public class VoidFileNotFoundException : Exception
{
public VoidFileNotFoundException(Guid id)

View File

@ -0,0 +1,17 @@
namespace VoidCat.Model.Exceptions;
/// <summary>
/// Specified id was not in the correct format
/// </summary>
public class VoidInvalidIdException : Exception
{
public VoidInvalidIdException(string id)
{
Id = id;
}
/// <summary>
/// The id in question
/// </summary>
public string Id { get; }
}

View File

@ -1,5 +1,8 @@
namespace VoidCat.Model.Exceptions;
/// <summary>
/// Operation is not allowed
/// </summary>
public class VoidNotAllowedException : Exception
{
public VoidNotAllowedException(string message) : base(message)

View File

@ -1,10 +1,10 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using Amazon;
using Amazon.Runtime;
using Amazon.S3;
using VoidCat.Model.Exceptions;
namespace VoidCat.Model;
@ -42,7 +42,9 @@ public static class Extensions
public static Guid FromBase58Guid(this string base58)
{
var enc = new NBitcoin.DataEncoders.Base58Encoder();
return new Guid(enc.DecodeData(base58));
var guidBytes = enc.DecodeData(base58);
if (guidBytes.Length != 16) throw new VoidInvalidIdException(base58);
return new Guid(guidBytes);
}
public static string ToBase58(this Guid id)

View File

@ -1,6 +1,8 @@
using System.Reflection;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Newtonsoft.Json;
using Prometheus;
using StackExchange.Redis;
@ -48,6 +50,32 @@ if (useRedis)
}
services.AddHttpClient();
services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
In = ParameterLocation.Header,
Description = "Please insert JWT with Bearer into field",
Name = "Authorization",
Type = SecuritySchemeType.ApiKey
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
var path = Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml");
c.IncludeXmlComments(path);
});
services.AddCors(opt =>
{
opt.AddDefaultPolicy(p =>
@ -76,7 +104,10 @@ services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
};
});
services.AddAuthorization((opt) => { opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); }); });
services.AddAuthorization((opt) =>
{
opt.AddPolicy(Policies.RequireAdmin, (auth) => { auth.RequireRole(Roles.Admin); });
});
// void.cat services
//
@ -135,6 +166,8 @@ app.UseStaticFiles();
app.UseRouting();
app.UseCors();
app.UseSwagger();
app.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
@ -148,4 +181,4 @@ app.UseEndpoints(ep =>
#endif
});
app.Run();
app.Run();

View File

@ -86,9 +86,12 @@ public class UserStore : IUserStore
var oldUser = await Get<InternalVoidUser>(newUser.Id);
if (oldUser == null) return;
//retain flags
var isEmailVerified = oldUser.Flags.HasFlag(VoidUserFlags.EmailVerified);
// update only a few props
oldUser.Avatar = newUser.Avatar;
oldUser.Flags = newUser.Flags;
oldUser.Flags = newUser.Flags | (isEmailVerified ? VoidUserFlags.EmailVerified : 0);
oldUser.DisplayName = newUser.DisplayName;
await Set(newUser.Id, oldUser);

View File

@ -9,6 +9,7 @@
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
<HostSPA>True</HostSPA>
<DefineConstants Condition="'$(HostSPA)' == 'True'">$(DefineConstants);HostSPA</DefineConstants>
<DocumentationFile>$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>