using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Hybrid; using Moonlight.Api.Database; using Moonlight.Api.Database.Entities; using Moonlight.Api.Implementations.ApiKeyScheme; using Moonlight.Api.Mappers; using Moonlight.Shared; using Moonlight.Shared.Http.Requests; using Moonlight.Shared.Http.Requests.Admin.ApiKeys; using Moonlight.Shared.Http.Responses; using Moonlight.Shared.Http.Responses.Admin.ApiKeys; namespace Moonlight.Api.Http.Controllers.Admin; [Authorize] [ApiController] [Route("api/admin/apiKeys")] public class ApiKeyController : Controller { private readonly DatabaseRepository KeyRepository; private readonly HybridCache HybridCache; public ApiKeyController(DatabaseRepository keyRepository, HybridCache hybridCache) { KeyRepository = keyRepository; HybridCache = hybridCache; } [HttpGet] [Authorize(Policy = Permissions.ApiKeys.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 = KeyRepository.Query(); // Filters if (filterOptions != null) { foreach (var filterOption in filterOptions.Filters) { query = filterOption.Key switch { nameof(ApiKey.Name) => query.Where(k => EF.Functions.ILike(k.Name, $"%{filterOption.Value}%")), nameof(ApiKey.Description) => query.Where(k => EF.Functions.ILike(k.Description, $"%{filterOption.Value}%")), _ => query }; } } // Pagination var data = await query .OrderBy(k => k.Id) .ProjectToDto() .Skip(startIndex) .Take(length) .ToArrayAsync(); var total = await query.CountAsync(); return new PagedData(data, total); } [HttpGet("{id:int}")] [Authorize(Policy = Permissions.ApiKeys.View)] public async Task> GetAsync([FromRoute] int id) { var key = await KeyRepository .Query() .FirstOrDefaultAsync(k => k.Id == id); if (key == null) return Problem("No API key with this id found", statusCode: 404); return ApiKeyMapper.ToDto(key); } [HttpPost] [Authorize(Policy = Permissions.ApiKeys.Create)] public async Task> CreateAsync([FromBody] CreateApiKeyDto request) { var apiKey = ApiKeyMapper.ToEntity(request); apiKey.Key = Guid.NewGuid().ToString("N").Substring(0, 32); var finalKey = await KeyRepository.AddAsync(apiKey); return ApiKeyMapper.ToDto(finalKey); } [HttpPatch("{id:int}")] [Authorize(Policy = Permissions.ApiKeys.Edit)] public async Task> UpdateAsync([FromRoute] int id, [FromBody] UpdateApiKeyDto request) { var apiKey = await KeyRepository .Query() .FirstOrDefaultAsync(k => k.Id == id); if (apiKey == null) return Problem("No API key with this id found", statusCode: 404); ApiKeyMapper.Merge(apiKey, request); await KeyRepository.UpdateAsync(apiKey); await HybridCache.RemoveAsync(string.Format(ApiKeySchemeHandler.CacheKeyFormat, apiKey.Key)); return ApiKeyMapper.ToDto(apiKey); } [HttpDelete("{id:int}")] [Authorize(Policy = Permissions.ApiKeys.Delete)] public async Task DeleteAsync([FromRoute] int id) { var apiKey = await KeyRepository .Query() .FirstOrDefaultAsync(k => k.Id == id); if (apiKey == null) return Problem("No API key with this id found", statusCode: 404); await KeyRepository.RemoveAsync(apiKey); await HybridCache.RemoveAsync(string.Format(ApiKeySchemeHandler.CacheKeyFormat, apiKey.Key)); return NoContent(); } }