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 DatabaseRepository; private readonly HybridCache Cache; public CrudController( DatabaseRepository databaseRepository, HybridCache cache ) { DatabaseRepository = databaseRepository; Cache = cache; } [HttpGet] [Authorize(Policy = Permissions.Nodes.View)] public async Task>> 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(data, total); } [HttpGet("{id:int}")] [Authorize(Policy = Permissions.Nodes.View)] public async Task> 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> 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> 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 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(); } }