Files
Servers/MoonlightServers.Api/Admin/Nodes/CrudController.cs

158 lines
4.6 KiB
C#

using System.Text;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Hybrid;
using Moonlight.Shared.Http.Requests;
using Moonlight.Shared.Http.Responses;
using MoonlightServers.Api.Infrastructure.Database;
using MoonlightServers.Api.Infrastructure.Database.Entities;
using MoonlightServers.Api.Infrastructure.Implementations.NodeToken;
using MoonlightServers.Shared;
using MoonlightServers.Shared.Admin.Nodes;
namespace MoonlightServers.Api.Admin.Nodes;
[ApiController]
[Route("api/admin/servers/nodes")]
public class CrudController : Controller
{
private readonly DatabaseRepository<Node> DatabaseRepository;
private readonly HybridCache Cache;
public CrudController(
DatabaseRepository<Node> databaseRepository,
HybridCache cache
)
{
DatabaseRepository = databaseRepository;
Cache = cache;
}
[HttpGet]
[Authorize(Policy = Permissions.Nodes.View)]
public async Task<ActionResult<PagedData<NodeDto>>> GetAsync(
[FromQuery] int startIndex,
[FromQuery] int length,
[FromQuery] FilterOptions? filterOptions
)
{
// Validation
if (startIndex < 0)
return Problem("Invalid start index specified", statusCode: 400);
if (length is < 1 or > 100)
return Problem("Invalid length specified");
// Query building
var query = DatabaseRepository
.Query();
// Filters
if (filterOptions != null)
{
foreach (var filterOption in filterOptions.Filters)
{
query = filterOption.Key switch
{
nameof(Node.Name) =>
query.Where(role => EF.Functions.ILike(role.Name, $"%{filterOption.Value}%")),
_ => query
};
}
}
// Pagination
var data = await query
.OrderBy(x => x.Id)
.ProjectToDto()
.Skip(startIndex)
.Take(length)
.ToArrayAsync();
var total = await query.CountAsync();
return new PagedData<NodeDto>(data, total);
}
[HttpGet("{id:int}")]
[Authorize(Policy = Permissions.Nodes.View)]
public async Task<ActionResult<NodeDto>> GetAsync([FromRoute] int id)
{
var node = await DatabaseRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (node == null)
return Problem("No node with this id found", statusCode: 404);
return NodeMapper.ToDto(node);
}
[HttpPost]
[Authorize(Policy = Permissions.Nodes.Create)]
public async Task<ActionResult<NodeDto>> CreateAsync([FromBody] CreateNodeDto request)
{
var node = NodeMapper.ToEntity(request);
node.TokenId = GenerateString(10);
node.Token = GenerateString(64);
var finalRole = await DatabaseRepository.AddAsync(node);
return NodeMapper.ToDto(finalRole);
}
[HttpPut("{id:int}")]
[Authorize(Policy = Permissions.Nodes.Edit)]
public async Task<ActionResult<NodeDto>> UpdateAsync([FromRoute] int id, [FromBody] UpdateNodeDto request)
{
var node = await DatabaseRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (node == null)
return Problem("No node with this id found", statusCode: 404);
NodeMapper.Merge(node, request);
await DatabaseRepository.UpdateAsync(node);
return NodeMapper.ToDto(node);
}
[HttpDelete("{id:int}")]
[Authorize(Policy = Permissions.Nodes.Delete)]
public async Task<ActionResult> DeleteAsync([FromRoute] int id)
{
var node = await DatabaseRepository
.Query()
.FirstOrDefaultAsync(x => x.Id == id);
if (node == null)
return Problem("No node with this id found", statusCode: 404);
await DatabaseRepository.RemoveAsync(node);
// Remove cache for node token auth scheme
await Cache.RemoveAsync(string.Format(NodeTokenSchemeHandler.CacheKeyFormat, node.TokenId));
return NoContent();
}
private static string GenerateString(int length)
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var stringBuilder = new StringBuilder();
var random = new Random();
for (var i = 0; i < length; i++)
{
stringBuilder.Append(chars[random.Next(chars.Length)]);
}
return stringBuilder.ToString();
}
}