diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs index 2b09577d..f7075e97 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/ApiKeys/ApiKeysController.cs @@ -1,8 +1,9 @@ -using Microsoft.AspNetCore.Mvc; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; -using MoonCore.Extended.Helpers; using MoonCore.Extended.PermFilter; -using MoonCore.Helpers; using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; using Moonlight.ApiServer.Services; @@ -15,26 +16,70 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.ApiKeys; [Route("api/admin/apikeys")] public class ApiKeysController : Controller { - private readonly CrudHelper CrudHelper; private readonly DatabaseRepository ApiKeyRepository; private readonly ApiKeyService ApiKeyService; - public ApiKeysController(CrudHelper crudHelper, DatabaseRepository apiKeyRepository, ApiKeyService apiKeyService) + public ApiKeysController(DatabaseRepository apiKeyRepository, ApiKeyService apiKeyService) { - CrudHelper = crudHelper; ApiKeyRepository = apiKeyRepository; ApiKeyService = apiKeyService; } - + [HttpGet] [RequirePermission("admin.apikeys.read")] - public async Task> Get([FromQuery] int page, [FromQuery] int pageSize = 50) - => await CrudHelper.Get(page, pageSize); + public async Task> Get( + [FromQuery] int page, + [FromQuery] [Range(1, 100)] int pageSize = 50 + ) + { + var count = await ApiKeyRepository.Get().CountAsync(); + + var apiKeys = await ApiKeyRepository + .Get() + .OrderBy(x => x.Id) + .Skip(page * pageSize) + .Take(pageSize) + .ToArrayAsync(); + + var mappedApiKey = apiKeys + .Select(x => new ApiKeyResponse() + { + Id = x.Id, + PermissionsJson = x.PermissionsJson, + Description = x.Description, + ExpiresAt = x.ExpiresAt + }) + .ToArray(); + + return new PagedData() + { + CurrentPage = page, + Items = mappedApiKey, + PageSize = pageSize, + TotalItems = count, + TotalPages = count == 0 ? 0 : (count - 1) / pageSize + }; + } [HttpGet("{id}")] [RequirePermission("admin.apikeys.read")] - public async Task GetSingle(int id) - => await CrudHelper.GetSingle(id); + public async Task GetSingle(int id) + { + var apiKey = await ApiKeyRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (apiKey == null) + throw new HttpApiException("No api key with that id found", 404); + + return new ApiKeyResponse() + { + Id = apiKey.Id, + PermissionsJson = apiKey.PermissionsJson, + Description = apiKey.Description, + ExpiresAt = apiKey.ExpiresAt + }; + } [HttpPost] [RequirePermission("admin.apikeys.create")] @@ -49,20 +94,55 @@ public class ApiKeysController : Controller var finalApiKey = await ApiKeyRepository.Add(apiKey); - var response = Mapper.Map(finalApiKey); + var response = new CreateApiKeyResponse + { + Id = finalApiKey.Id, + PermissionsJson = finalApiKey.PermissionsJson, + Description = finalApiKey.Description, + ExpiresAt = finalApiKey.ExpiresAt, + Secret = ApiKeyService.GenerateJwt(finalApiKey) + }; - response.Secret = ApiKeyService.GenerateJwt(finalApiKey); - return response; } [HttpPatch("{id}")] [RequirePermission("admin.apikeys.update")] - public async Task Update([FromRoute] int id, [FromBody] UpdateApiKeyRequest request) - => await CrudHelper.Update(id, request); + public async Task Update([FromRoute] int id, [FromBody] UpdateApiKeyRequest request) + { + var apiKey = await ApiKeyRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (apiKey == null) + throw new HttpApiException("No api key with that id found", 404); + + apiKey.Description = request.Description; + apiKey.PermissionsJson = request.PermissionsJson; + apiKey.ExpiresAt = request.ExpiresAt; + + await ApiKeyRepository.Update(apiKey); + + return new ApiKeyResponse() + { + Id = apiKey.Id, + Description = apiKey.Description, + PermissionsJson = apiKey.PermissionsJson, + ExpiresAt = apiKey.ExpiresAt + }; + } [HttpDelete("{id}")] [RequirePermission("admin.apikeys.delete")] public async Task Delete([FromRoute] int id) - => await CrudHelper.Delete(id); + { + var apiKey = await ApiKeyRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (apiKey == null) + throw new HttpApiException("No api key with that id found", 404); + + await ApiKeyRepository.Remove(apiKey); + } } \ No newline at end of file diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs index c7b72399..9d02396d 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs @@ -1,4 +1,6 @@ +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; using MoonCore.Exceptions; using MoonCore.Extended.Abstractions; using MoonCore.Extended.Helpers; @@ -14,74 +16,156 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Users; [Route("api/admin/users")] public class UsersController : Controller { - private readonly CrudHelper CrudHelper; private readonly DatabaseRepository UserRepository; - public UsersController(CrudHelper crudHelper, DatabaseRepository userRepository) + public UsersController(DatabaseRepository userRepository) { - CrudHelper = crudHelper; UserRepository = userRepository; } [HttpGet] [RequirePermission("admin.users.read")] - public async Task> Get([FromQuery] int page, [FromQuery] int pageSize = 50) - => await CrudHelper.Get(page, pageSize); + public async Task> Get( + [FromQuery] int page, + [FromQuery] [Range(1, 100)] int pageSize = 50 + ) + { + var count = await UserRepository.Get().CountAsync(); + + var users = await UserRepository + .Get() + .OrderBy(x => x.Id) + .Skip(page * pageSize) + .Take(pageSize) + .ToArrayAsync(); + + var mappedUsers = users + .Select(x => new UserResponse() + { + Id = x.Id, + Email = x.Email, + Username = x.Username + }) + .ToArray(); + + return new PagedData() + { + CurrentPage = page, + Items = mappedUsers, + PageSize = pageSize, + TotalItems = count, + TotalPages = count == 0 ? 0 : (count - 1) / pageSize + }; + } [HttpGet("{id}")] [RequirePermission("admin.users.read")] - public async Task GetSingle(int id) - => await CrudHelper.GetSingle(id); + public async Task GetSingle(int id) + { + var user = await UserRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (user == null) + throw new HttpApiException("No user with that id found", 404); + + return new UserResponse() + { + Id = user.Id, + Email = user.Email, + Username = user.Username + }; + } [HttpPost] [RequirePermission("admin.users.create")] - public async Task Create([FromBody] CreateUserRequest request) + public async Task Create([FromBody] CreateUserRequest request) { // Reformat values request.Username = request.Username.ToLower().Trim(); request.Email = request.Email.ToLower().Trim(); - + // Check for users with the same values if (UserRepository.Get().Any(x => x.Username == request.Username)) throw new HttpApiException("A user with that username already exists", 400); - + if (UserRepository.Get().Any(x => x.Email == request.Email)) throw new HttpApiException("A user with that email address already exists", 400); - request.Password = HashHelper.Hash(request.Password); + var hashedPassword = HashHelper.Hash(request.Password); - return await CrudHelper.Create(request); + var user = new User() + { + Email = request.Email, + Username = request.Username, + Password = hashedPassword, + PermissionsJson = request.PermissionsJson + }; + + var finalUser = await UserRepository.Add(user); + + return new UserResponse() + { + Id = finalUser.Id, + Email = finalUser.Email, + Username = finalUser.Username + }; } [HttpPatch("{id}")] [RequirePermission("admin.users.update")] - public async Task Update([FromRoute] int id, [FromBody] UpdateUserRequest request) + public async Task Update([FromRoute] int id, [FromBody] UpdateUserRequest request) { - var user = await CrudHelper.GetSingleModel(id); - + var user = await UserRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (user == null) + throw new HttpApiException("No user with that id found", 404); + // Reformat values request.Username = request.Username.ToLower().Trim(); request.Email = request.Email.ToLower().Trim(); - + // Check for users with the same values if (UserRepository.Get().Any(x => x.Username == request.Username && x.Id != user.Id)) throw new HttpApiException("A user with that username already exists", 400); - + if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id)) throw new HttpApiException("A user with that email address already exists", 400); - + // Perform hashing the password if required if (!string.IsNullOrEmpty(request.Password)) { - request.Password = HashHelper.Hash(request.Password); + user.Password = HashHelper.Hash(request.Password); user.TokenValidTimestamp = DateTime.UtcNow; // This change will get applied by the crud helper } - - return await CrudHelper.Update(user, request); + + user.Email = request.Email; + user.Username = request.Username; + // TODO: Add permissions update here + + await UserRepository.Update(user); + + return new UserResponse() + { + Id = user.Id, + Email = user.Email, + Username = user.Username + }; } [HttpDelete("{id}")] [RequirePermission("admin.users.delete")] public async Task Delete([FromRoute] int id) - => await CrudHelper.Delete(id); + { + var user = await UserRepository + .Get() + .FirstOrDefaultAsync(x => x.Id == id); + + if (user == null) + throw new HttpApiException("No user with that id found", 404); + + await UserRepository.Remove(user); + } } \ No newline at end of file diff --git a/Moonlight.Client/UI/Views/Admin/Api/Index.razor b/Moonlight.Client/UI/Views/Admin/Api/Index.razor index 68a85b40..9388f837 100644 --- a/Moonlight.Client/UI/Views/Admin/Api/Index.razor +++ b/Moonlight.Client/UI/Views/Admin/Api/Index.razor @@ -53,18 +53,18 @@ - + - + - - - + + + @(Formatter.FormatDate(context.ExpiresAt)) - +
@@ -83,19 +83,19 @@ @code { - private DataTable Table; + private DataTable Table; - private async Task> LoadData(PaginationOptions options) - => await ApiClient.GetJson>($"api/admin/apikeys?page={options.Page}&pageSize={options.PerPage}"); + private async Task> LoadData(PaginationOptions options) + => await ApiClient.GetJson>($"api/admin/apikeys?page={options.Page}&pageSize={options.PerPage}"); - private async Task Delete(ApiKeyDetailResponse apiKeyDetailResponse) + private async Task Delete(ApiKeyResponse apiKeyResponse) { await AlertService.ConfirmDanger( "API Key deletion", - $"Do you really want to delete the api key '{apiKeyDetailResponse.Description}'", + $"Do you really want to delete the api key '{apiKeyResponse.Description}'", async () => { - await ApiClient.Delete($"api/admin/apikeys/{apiKeyDetailResponse.Id}"); + await ApiClient.Delete($"api/admin/apikeys/{apiKeyResponse.Id}"); await ToastService.Success("Successfully deleted api key"); await Table.Refresh(); diff --git a/Moonlight.Client/UI/Views/Admin/Api/Update.razor b/Moonlight.Client/UI/Views/Admin/Api/Update.razor index f6d3f07b..d12eec9e 100644 --- a/Moonlight.Client/UI/Views/Admin/Api/Update.razor +++ b/Moonlight.Client/UI/Views/Admin/Api/Update.razor @@ -55,7 +55,7 @@ private async Task Load(LazyLoader _) { - var detail = await ApiClient.GetJson($"api/admin/apikeys/{Id}"); + var detail = await ApiClient.GetJson($"api/admin/apikeys/{Id}"); Request = Mapper.Map(detail); } diff --git a/Moonlight.Client/UI/Views/Admin/Users/Index.razor b/Moonlight.Client/UI/Views/Admin/Users/Index.razor index 305d6aa4..df596025 100644 --- a/Moonlight.Client/UI/Views/Admin/Users/Index.razor +++ b/Moonlight.Client/UI/Views/Admin/Users/Index.razor @@ -17,14 +17,14 @@
- + - + - - - - + + + +
@@ -43,19 +43,19 @@ @code { - private DataTable Table; + private DataTable Table; - private async Task> LoadData(PaginationOptions options) - => await ApiClient.GetJson>($"api/admin/users?page={options.Page}&pageSize={options.PerPage}"); + private async Task> LoadData(PaginationOptions options) + => await ApiClient.GetJson>($"api/admin/users?page={options.Page}&pageSize={options.PerPage}"); - private async Task Delete(UserDetailResponse detailResponse) + private async Task Delete(UserResponse response) { await AlertService.ConfirmDanger( "User deletion", - $"Do you really want to delete the user '{detailResponse.Username}'", + $"Do you really want to delete the user '{response.Username}'", async () => { - await ApiClient.Delete($"api/admin/users/{detailResponse.Id}"); + await ApiClient.Delete($"api/admin/users/{response.Id}"); await ToastService.Success("Successfully deleted user"); await Table.Refresh(); diff --git a/Moonlight.Client/UI/Views/Admin/Users/Update.razor b/Moonlight.Client/UI/Views/Admin/Users/Update.razor index dd3fe955..bd4ef88a 100644 --- a/Moonlight.Client/UI/Views/Admin/Users/Update.razor +++ b/Moonlight.Client/UI/Views/Admin/Users/Update.razor @@ -55,7 +55,7 @@ private async Task Load(LazyLoader _) { - var detail = await ApiClient.GetJson($"api/admin/users/{Id}"); + var detail = await ApiClient.GetJson($"api/admin/users/{Id}"); Request = Mapper.Map(detail); } diff --git a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDetailResponse.cs b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs similarity index 87% rename from Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDetailResponse.cs rename to Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs index 0f6c1aa3..826cac2b 100644 --- a/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyDetailResponse.cs +++ b/Moonlight.Shared/Http/Responses/Admin/ApiKeys/ApiKeyResponse.cs @@ -1,6 +1,6 @@ namespace Moonlight.Shared.Http.Responses.Admin.ApiKeys; -public class ApiKeyDetailResponse +public class ApiKeyResponse { public int Id { get; set; } public string Description { get; set; } diff --git a/Moonlight.Shared/Http/Responses/Admin/Users/UserDetailResponse.cs b/Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs similarity index 84% rename from Moonlight.Shared/Http/Responses/Admin/Users/UserDetailResponse.cs rename to Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs index 1cb6b12b..12cd0e2d 100644 --- a/Moonlight.Shared/Http/Responses/Admin/Users/UserDetailResponse.cs +++ b/Moonlight.Shared/Http/Responses/Admin/Users/UserResponse.cs @@ -1,6 +1,6 @@ namespace Moonlight.Shared.Http.Responses.Admin.Users; -public class UserDetailResponse +public class UserResponse { public int Id { get; set; } public string Username { get; set; } diff --git a/Moonlight.Shared/Http/Responses/Assets/FrontendAssetResponse.cs b/Moonlight.Shared/Http/Responses/Assets/FrontendAssetResponse.cs deleted file mode 100644 index 8eb17cb3..00000000 --- a/Moonlight.Shared/Http/Responses/Assets/FrontendAssetResponse.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.Assets; - -public class FrontendAssetResponse -{ - public string[] JavascriptFiles { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs b/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs deleted file mode 100644 index 767df61c..00000000 --- a/Moonlight.Shared/Http/Responses/ClientPlugins/ClientPluginsResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.ClientPlugins; - -public class ClientPluginsResponse -{ - public string[] Dlls { get; set; } - public string CacheKey { get; set; } -} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Responses/PluginsStream/PluginsAssetManifest.cs b/Moonlight.Shared/Http/Responses/PluginsStream/PluginsAssetManifest.cs deleted file mode 100644 index 1dfa5ffb..00000000 --- a/Moonlight.Shared/Http/Responses/PluginsStream/PluginsAssetManifest.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Moonlight.Shared.Http.Responses.PluginsStream; - -public class PluginsAssetManifest -{ - public string[] CssFiles { get; set; } - public string[] JavascriptFiles { get; set; } -} \ No newline at end of file