Implemented handling of server side issues using the rfc for problem detasils in the frontend
This commit is contained in:
30
Moonlight.Frontend/Helpers/ProblemDetailsHelper.cs
Normal file
30
Moonlight.Frontend/Helpers/ProblemDetailsHelper.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System.Net.Http.Json;
|
||||
using Microsoft.AspNetCore.Components.Forms;
|
||||
using Moonlight.Shared.Http.Responses;
|
||||
|
||||
namespace Moonlight.Frontend.Helpers;
|
||||
|
||||
public static class ProblemDetailsHelper
|
||||
{
|
||||
public static async Task HandleProblemDetailsAsync(HttpResponseMessage response, object model, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
var problemDetails = await response.Content.ReadFromJsonAsync<ProblemDetails>();
|
||||
|
||||
if (problemDetails == null)
|
||||
response.EnsureSuccessStatusCode(); // Trigger exception when unable to parse
|
||||
else
|
||||
{
|
||||
if(!string.IsNullOrEmpty(problemDetails.Detail))
|
||||
validationMessageStore.Add(new FieldIdentifier(model, string.Empty), problemDetails.Detail);
|
||||
|
||||
if (problemDetails.Errors != null)
|
||||
{
|
||||
foreach (var error in problemDetails.Errors)
|
||||
{
|
||||
foreach (var message in error.Value)
|
||||
validationMessageStore.Add(new FieldIdentifier(model, error.Key), message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,18 @@
|
||||
@using Moonlight.Frontend.UI.Admin.Components
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.UI.Admin.Components
|
||||
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||
@using Moonlight.Shared.Http.Responses
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create new API key</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -44,7 +50,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<CreateApiKeyDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
||||
|
||||
private CreateApiKeyDto Request;
|
||||
|
||||
@@ -61,8 +67,25 @@
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
|
||||
var response = await HttpClient.PostAsJsonAsync(
|
||||
"/api/admin/apiKeys",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await OnSubmit.Invoke(Request);
|
||||
await ToastService.SuccessAsync(
|
||||
"API Key creation",
|
||||
$"Successfully created API key {Request.Name}"
|
||||
);
|
||||
|
||||
await OnSubmit.Invoke();
|
||||
|
||||
await CloseAsync();
|
||||
return true;
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.UI.Admin.Components
|
||||
@using Moonlight.Shared.Http.Requests.Roles
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Create new role
|
||||
@@ -49,7 +54,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<CreateRoleDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
||||
|
||||
private CreateRoleDto Request;
|
||||
private List<string> Permissions;
|
||||
@@ -67,8 +72,22 @@
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
|
||||
var response = await HttpClient.PostAsJsonAsync(
|
||||
"api/admin/roles",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await OnSubmit.Invoke(Request);
|
||||
await ToastService.SuccessAsync("Role creation", $"Role {Request.Name} has been successfully created");
|
||||
|
||||
await OnSubmit.Invoke();
|
||||
await CloseAsync();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
@using Moonlight.Shared.Http.Requests.Users
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Shared.Http.Requests.Users
|
||||
@using Moonlight.Shared.Http.Responses
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Create new user
|
||||
@@ -19,7 +25,7 @@
|
||||
|
||||
<FieldGroup>
|
||||
<FormValidationSummary/>
|
||||
<DataAnnotationsValidator/>
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<FieldSet>
|
||||
<Field>
|
||||
@@ -45,7 +51,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<CreateUserDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnCompleted { get; set; }
|
||||
|
||||
private CreateUserDto Request;
|
||||
|
||||
@@ -56,7 +62,24 @@
|
||||
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
await OnSubmit.Invoke(Request);
|
||||
var response = await HttpClient.PostAsJsonAsync(
|
||||
"/api/admin/users",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"User creation",
|
||||
$"Successfully created user {Request.Username}"
|
||||
);
|
||||
|
||||
await OnCompleted.Invoke();
|
||||
await CloseAsync();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Frontend.UI.Admin.Components
|
||||
@using Moonlight.Shared.Http.Requests.ApiKeys
|
||||
@using Moonlight.Shared.Http.Responses.ApiKeys
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>Update API key</DialogTitle>
|
||||
<DialogDescription>
|
||||
@@ -45,7 +50,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<UpdateApiKeyDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
||||
[Parameter] public ApiKeyDto Key { get; set; }
|
||||
|
||||
private UpdateApiKeyDto Request;
|
||||
@@ -60,7 +65,25 @@
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
await OnSubmit.Invoke(Request);
|
||||
|
||||
var response = await HttpClient.PatchAsJsonAsync(
|
||||
$"/api/admin/apiKeys/{Key.Id}",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"API Key update",
|
||||
$"Successfully updated API key {Request.Name}"
|
||||
);
|
||||
|
||||
await OnSubmit.Invoke();
|
||||
await CloseAsync();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Frontend.UI.Admin.Components
|
||||
@using Moonlight.Shared.Http.Requests.Roles
|
||||
@using Moonlight.Shared.Http.Responses.Admin
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Update @Role.Name
|
||||
@@ -51,7 +56,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<UpdateRoleDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnSubmit { get; set; }
|
||||
[Parameter] public RoleDto Role { get; set; }
|
||||
|
||||
private UpdateRoleDto Request;
|
||||
@@ -66,7 +71,22 @@
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
Request.Permissions = Permissions.ToArray();
|
||||
await OnSubmit.Invoke(Request);
|
||||
|
||||
var response = await HttpClient.PatchAsJsonAsync(
|
||||
$"api/admin/roles/{Role.Id}",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync("Role update", $"Role {Request.Name} has been successfully updated");
|
||||
|
||||
await OnSubmit.Invoke();
|
||||
await CloseAsync();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Shared.Http.Requests.Users
|
||||
@using Moonlight.Shared.Http.Responses
|
||||
@using Moonlight.Shared.Http.Responses.Users
|
||||
@using ShadcnBlazor.Dialogs
|
||||
@using ShadcnBlazor.Extras.Forms
|
||||
@using ShadcnBlazor.Extras.Toasts
|
||||
@using ShadcnBlazor.Fields
|
||||
@using ShadcnBlazor.Inputs
|
||||
|
||||
@inherits ShadcnBlazor.Extras.Dialogs.DialogBase
|
||||
|
||||
@inject HttpClient HttpClient
|
||||
@inject ToastService ToastService
|
||||
|
||||
<DialogHeader>
|
||||
<DialogTitle>
|
||||
Update @User.Username
|
||||
@@ -46,7 +52,7 @@
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter] public Func<UpdateUserDto, Task> OnSubmit { get; set; }
|
||||
[Parameter] public Func<Task> OnCompleted { get; set; }
|
||||
[Parameter] public UserDto User { get; set; }
|
||||
|
||||
private UpdateUserDto Request;
|
||||
@@ -58,7 +64,23 @@
|
||||
|
||||
private async Task<bool> OnSubmitAsync(EditContext editContext, ValidationMessageStore validationMessageStore)
|
||||
{
|
||||
await OnSubmit.Invoke(Request);
|
||||
var response = await HttpClient.PatchAsJsonAsync(
|
||||
$"/api/admin/users/{User.Id}",
|
||||
Request
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"User update",
|
||||
$"Successfully updated user {Request.Username}"
|
||||
);
|
||||
|
||||
await OnCompleted.Invoke();
|
||||
await CloseAsync();
|
||||
|
||||
return true;
|
||||
|
||||
@@ -123,19 +123,8 @@
|
||||
{
|
||||
await DialogService.LaunchAsync<CreateApiKeyDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async (CreateApiKeyDto dto) =>
|
||||
parameters[nameof(CreateApiKeyDialog.OnSubmit)] = async () =>
|
||||
{
|
||||
await HttpClient.PostAsJsonAsync(
|
||||
"/api/admin/apiKeys",
|
||||
dto,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"API Key creation",
|
||||
$"Successfully created API key {dto.Name}"
|
||||
);
|
||||
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
@@ -146,19 +135,8 @@
|
||||
await DialogService.LaunchAsync<UpdateApiKeyDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(UpdateApiKeyDialog.Key)] = key;
|
||||
parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async (UpdateApiKeyDto dto) =>
|
||||
parameters[nameof(UpdateApiKeyDialog.OnSubmit)] = async () =>
|
||||
{
|
||||
await HttpClient.PatchAsJsonAsync(
|
||||
$"/api/admin/apiKeys/{key.Id}",
|
||||
dto,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"API Key update",
|
||||
$"Successfully updated API key {dto.Name}"
|
||||
);
|
||||
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Moonlight.Shared
|
||||
@using LucideBlazor
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.Services
|
||||
@using Moonlight.Shared.Http.Requests.Themes
|
||||
@using ShadcnBlazor.Buttons
|
||||
@@ -118,11 +119,17 @@
|
||||
{
|
||||
Request.CssContent = await Editor.GetValueAsync();
|
||||
|
||||
await HttpClient.PostAsJsonAsync(
|
||||
var response = await HttpClient.PostAsJsonAsync(
|
||||
"/api/admin/themes",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"Theme creation",
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
@using Microsoft.AspNetCore.Authorization
|
||||
@using Moonlight.Shared
|
||||
@using LucideBlazor
|
||||
@using Moonlight.Frontend.Helpers
|
||||
@using Moonlight.Frontend.Mappers
|
||||
@using Moonlight.Frontend.Services
|
||||
@using Moonlight.Shared.Http.Requests.Themes
|
||||
@@ -132,11 +133,17 @@
|
||||
{
|
||||
Request.CssContent = await Editor.GetValueAsync();
|
||||
|
||||
await HttpClient.PatchAsJsonAsync(
|
||||
var response = await HttpClient.PatchAsJsonAsync(
|
||||
$"/api/admin/themes/{Theme.Id}",
|
||||
Request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
await ProblemDetailsHelper.HandleProblemDetailsAsync(response, Request, validationMessageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"Theme update",
|
||||
|
||||
@@ -134,15 +134,8 @@
|
||||
{
|
||||
await DialogService.LaunchAsync<CreateRoleDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task (CreateRoleDto request) =>
|
||||
parameters[nameof(CreateRoleDialog.OnSubmit)] = async Task () =>
|
||||
{
|
||||
await HttpClient.PostAsJsonAsync(
|
||||
"api/admin/roles",
|
||||
request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync("Role creation", $"Role {request.Name} has been successfully created");
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
@@ -153,15 +146,8 @@
|
||||
await DialogService.LaunchAsync<UpdateRoleDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(UpdateRoleDialog.Role)] = role;
|
||||
parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task (UpdateRoleDto request) =>
|
||||
parameters[nameof(UpdateRoleDialog.OnSubmit)] = async Task () =>
|
||||
{
|
||||
await HttpClient.PatchAsJsonAsync(
|
||||
$"api/admin/roles/{role.Id}",
|
||||
request,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync("Role update", $"Role {request.Name} has been successfully updated");
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -124,19 +124,8 @@
|
||||
{
|
||||
await DialogService.LaunchAsync<CreateUserDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(CreateUserDialog.OnSubmit)] = async (CreateUserDto dto) =>
|
||||
parameters[nameof(CreateUserDialog.OnCompleted)] = async () =>
|
||||
{
|
||||
await HttpClient.PostAsJsonAsync(
|
||||
"/api/admin/users",
|
||||
dto,
|
||||
Constants.SerializerOptions
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"User creation",
|
||||
$"Successfully created user {dto.Username}"
|
||||
);
|
||||
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
@@ -147,18 +136,8 @@
|
||||
await DialogService.LaunchAsync<UpdateUserDialog>(parameters =>
|
||||
{
|
||||
parameters[nameof(UpdateUserDialog.User)] = user;
|
||||
parameters[nameof(CreateUserDialog.OnSubmit)] = async (UpdateUserDto dto) =>
|
||||
parameters[nameof(UpdateUserDialog.OnCompleted)] = async () =>
|
||||
{
|
||||
await HttpClient.PatchAsJsonAsync(
|
||||
$"/api/admin/users/{user.Id}",
|
||||
dto
|
||||
);
|
||||
|
||||
await ToastService.SuccessAsync(
|
||||
"User update",
|
||||
$"Successfully updated user {dto.Username}"
|
||||
);
|
||||
|
||||
await Grid.RefreshAsync();
|
||||
};
|
||||
});
|
||||
|
||||
10
Moonlight.Shared/Http/Responses/ProblemDetails.cs
Normal file
10
Moonlight.Shared/Http/Responses/ProblemDetails.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Moonlight.Shared.Http.Responses;
|
||||
|
||||
public class ProblemDetails
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Title { get; set; }
|
||||
public int Status { get; set; }
|
||||
public string? Detail { get; set; }
|
||||
public Dictionary<string, string[]>? Errors { get; set; }
|
||||
}
|
||||
@@ -50,6 +50,9 @@ namespace Moonlight.Shared.Http;
|
||||
|
||||
// Container Helper
|
||||
[JsonSerializable(typeof(ContainerHelperStatusDto))]
|
||||
|
||||
// Misc
|
||||
[JsonSerializable(typeof(ProblemDetails))]
|
||||
public partial class SerializationContext : JsonSerializerContext
|
||||
{
|
||||
}
|
||||
Reference in New Issue
Block a user